Página 1 de 1

Padrão repository com xHarbour

Enviado: 10 Jan 2020 16:29
por bencz
Boas tardes!
Eu estava fazendo alguns testes aqui e resolvi implementar o padrão "repository" para testar a minha ideia...
O código que irei colocar aqui está bem 'feio', foi muito mais para teste do que para implementar algo com padrão de produção... mas, com poucas alterações é possivel colocar o código para padrão de produção...
Para quem não sabe exatamente o que é o padrão repository... basicamente existe uma classe "repository" que faz o acesso aos dados de uma unica tabela, para cada tabela é criada uma classe modelo, contendo as informações necessarias de cada tabela.
Na classe repository você tem acesso aos comandos basicos do SQL, como Update, Insert, GetById, GetAll, Delete ( no caso, implementei somente o insert, getall e o getbyid )

Segue o código:

teste.prg

Código: Selecionar todos

function start
    LOCAL oConnection := NIL
    
    // Inicializa a conexão, uma unica vez...
    oConnection := ConnectionFactory():New()
    
    TestInsert(oConnection)
    *TestGetAll(oConnection)
    TestGetById(oConnection)

return nil

function TestInsert(oConnection)  
    local i := 1
    local oRepoUsuarios := NIL
    oRepoUsuarios := UsuariosRepository():new(oConnection)

    for i := 1 to 10
      oUsuario := Usuarios():New()
      oUsuario:Nome := 'Alexandre ' + RTRIM(LTRIM(STR(I)))
      oUsuario:SobreNome := 'Bencz ' + RTRIM(LTRIM(STR(I)))
      
      oRepoUsuarios:Insert(oUsuario)
    next

return nil

function TestGetAll(oConnection)
    local aResult := NIL
    local oRepoUsuarios := NIL
    
    oRepoUsuarios := UsuariosRepository():new(oConnection)
    
    aResult := oRepoUsuarios:GetAll()
    for i := 1 to len(aResult)
        ? (aResult[i]):USUARIOID, (aResult[i]):NOME, (aResult[i]):SobreNome
    next
return nil

function TestGetById(oConnection)
        local aResult := NIL
    local oRepoUsuarios := NIL
    
    oRepoUsuarios := UsuariosRepository():new(oConnection)
    
    aResult := oRepoUsuarios:GetById(2)
    for i := 1 to len(aResult)
        ? (aResult[i]):USUARIOID, (aResult[i]):NOME, (aResult[i]):SobreNome
    next
return nil
connectionFactory.prg

Código: Selecionar todos

#include "hboo.ch"
#include "hbclass.ch"

#include "sqlrdd.ch"
    
REQUEST SQLRDD            
REQUEST SR_ODBC   

#define CONN_TYPE CONNECT_ODBC
#define CONN_DNS  "driver=SQL Server;network=dbmssocn;server=127.0.0.1;database=test;uid=sa;pwd=senha;"

class ConnectionFactory
private:
    DATA nConnection INIT NIL
    DATA oSQL INIT NIL
    
public:
    METHOD New(nConnectionType, cDNS)
    INLINE METHOD GetSql() 
        RETURN ::oSQL
    ENDMETHOD
endclass

method new(nConnectionType, cDNS) class ConnectionFactory
    Default(@nConnectionType, CONN_TYPE)
    Default(@cDNS, CONN_DNS)

    ::nConnection := SR_AddConnection(nConnectionType, cDNS)
    
    IF ::nConnection < 0
        Throw(ErrorNew('Falha ao inicializar a conecao', 0, 0, 'ConnectionFactory'))
    ENDIF
    
    ::oSQL := SR_GetConnection(::nConnection)

return self
dataaccess.prg

Código: Selecionar todos

#include "hboo.ch"
#include "hbclass.ch"        
    
class DataAccess
private:
    data oConnectionFactory init nil
    data cModelClassName init nil

public:
    method new() constructor
    
    method GetAll()
    method GetById(nId)
    method Insert(oModelClass)
    
endclass

method new(cModelClassName, oConnectionFactory) class DataAccess   
    ::cModelClassName := cModelClassName
    ::oConnectionFactory := oConnectionFactory
return self

method GetAll() class DataAccess
    LOCAL I := 0, J := 0
    LOCAL cQuery := ""
    LOCAL nError := NIL
    LOCAL nErrorPosition := NIL
    LOCAL aReturn := {}
    LOCAL aTmpReturn := {}
    LOCAL aTmpDbStructure := NIL
    LOCAL oTmpObj := NIL

    cQuery := "SELECT * FROM " + ::cModelClassName
    
    apCode := SR_SQLParse(cQuery, @nError, @nErrorPosition)
    ::oConnectionFactory:GetSql():exec( SR_SQLCodeGen(apCode, {}, ::oConnectionFactory:GetSql():nSystemID ), , .t. , @aReturn )
    
    for i := 1 to len(aReturn)   
        aadd(aTmpReturn, &('Create' + ::cModelClassName + 'Instance()') )
        aTmpDbStructure := aTmpReturn[i]:GetDbStructure()
        
        for j := 1 to len(aReturn[i])
            Eval(aTmpDbStructure[j][5], aReturn[i][j])
        next        
    next
    
return aTmpReturn

method GetById(nId) class DataAccess

    LOCAL I := 0, J := 0
    LOCAL cQuery := ""
    LOCAL nError := NIL
    LOCAL nErrorPosition := NIL
    LOCAL aReturn := {}
    LOCAL aTmpReturn := {}
    LOCAL aTmpDbStructure := NIL
    LOCAL oTmpObj := NIL
    LOCAL oModelClass := NIL

    oModelClass := &('Create' + ::cModelClassName + 'Instance()')
    cQuery := "SELECT * FROM " + ::cModelClassName + " WHERE " + oModelClass:GetKeyField() + " = ?" 
    
    apCode := SR_SQLParse(cQuery, @nError, @nErrorPosition)
    ::oConnectionFactory:GetSql():exec( SR_SQLCodeGen(apCode, {nId}, ::oConnectionFactory:GetSql():nSystemID ), , .t. , @aReturn )
    
    for i := 1 to len(aReturn)   
        IF(I > 1)   
            aadd(aTmpReturn, &('Create' + ::cModelClassName + 'Instance()') )
        ELSE
            aadd(aTmpReturn, oModelClass )
        ENDIF
        
        aTmpDbStructure := aTmpReturn[i]:GetDbStructure()
        for j := 1 to len(aReturn[i])
            Eval(aTmpDbStructure[j][5], aReturn[i][j])
        next        
    next

return aTmpReturn

method Insert(oModelClass) class DataAccess
    LOCAL I := 1
    LOCAL aObjetos := NIL
    LOCAL tField := NIL
    LOCAL cQuery := ''
    LOCAL nError := NIL
    LOCAL nErrorPosition := NIL
    LOCAL apCode := NIL
    LOCAL oPreparedQuery := NIL
    LOCAL aParameters := {}
    
    aObjetos := __objGetValueList(oModelClass, nil, HB_OO_CLSTP_EXPORTED)

    cQuery := 'INSERT INTO ' + ::cModelClassName + "("
    
    FOR I := 1 TO LEN(aObjetos)
        IF aObjetos[i][HB_OO_DATA_SYMBOL] == oModelClass:GetKeyField()
            LOOP
        ENDIF
        
        IF(I > 1)
            cQuery += ', '
        ENDIF
        
        cQuery += aObjetos[i][HB_OO_DATA_SYMBOL]
        
        // Adiciona os parametros
        aadd(aParameters, aObjetos[i][HB_OO_DATA_VALUE])
    NEXT
    
    cQuery += ') VALUES('
    
    FOR I := 1 TO LEN(aObjetos)
        IF aObjetos[i][HB_OO_DATA_SYMBOL] == oModelClass:GetKeyField()
            LOOP
        ENDIF
    
        IF(I > 1)
            cQuery += ', '
        ENDIF
        
        cQuery += '?'   
    NEXT
    
    cQuery += ')'
           
    apCode := SR_SQLParse(cQuery, @nError, @nErrorPosition)
    ::oConnectionFactory:GetSql():exec( SR_SQLCodeGen(apCode, aParameters, ::oConnectionFactory:GetSql():nSystemID ) )
    ::oConnectionFactory:GetSql():Commit()
    
return nil
Modelos\Usuario.prg

Código: Selecionar todos

#include "hboo.ch"
#include "hbclass.ch"

function CreateUsuariosInstance() ; return Usuarios():New()

class Usuarios
public:
    DATA USUARIOID INIT NIL
    DATA NOME INIT NIL
    DATA SOBRENOME INIT NIL
        
private:
    DATA KEYFIELD INIT "USUARIOID"
    DATA DBSTRUCTURE INIT {} 
    
public:
    METHOD NEW() 
    
    INLINE METHOD GetKeyField() ; return ::KEYFIELD ; endmethod
    INLINE METHOD GetDbStructure() ; return ::DBSTRUCTURE ; endmethod
    
    METHOD SetUsuarioID(nUsuarioID) INLINE ( ::USUARIOID := nUsuarioID ) 
    METHOD SetNome(cNome) INLINE ( ::NOME := cNome )   
    METHOD SetSobreNome(cSobreNome) INLINE ( ::SOBRENOME := cSobreNome )   
endclass

METHOD NEW() CLASS Usuarios

    aadd(::DBSTRUCTURE, { 'USUARIOID', 'N', -1, .T. , {| n | ::SetUsuarioID(n)  } })
    aadd(::DBSTRUCTURE, { 'NOME',      'C', 50, .F. , {| c | ::SetNome(c)       } })
    aadd(::DBSTRUCTURE, { 'SOBRENOME', 'C', 50, .F. , {| c | ::SetSobreNome(c)  } })

RETURN SELF
repository\UsuariosRepository.prg

Código: Selecionar todos

#include "hbclass.ch"

class UsuariosRepository INHERIT DataAccess
public:
    method new(oConnectionFactory) inline ( Super:New('Usuarios', IIF( oConnectionFactory == NIL, ConnectionFactory():NEW(), oConnectionFactory) ) ) 
endclass  
A tabela de testes que utilizei no MS-SQL é:

Código: Selecionar todos

CREATE TABLE [dbo].[Usuarios](
	[UsuarioID] [bigint] IDENTITY(1,1) NOT NULL,
	[Nome] [nvarchar](50) NOT NULL,
	[SobreNome] [nvarchar](50) NOT NULL,
 CONSTRAINT [PK_Usuarios] PRIMARY KEY CLUSTERED 
(
	[UsuarioID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
Dentro da classe repository da tabela, é possivel adicionar metodos para fazer selects mais elaborados ou inserts mais elaborados... o lado bom desse padrão é que fica tudo organizadinho em seu lugar....

Padrão repository com xHarbour

Enviado: 10 Jan 2020 17:40
por JoséQuintas
Sei lá se entendi o uso....
Mais prático usar ADO, ou direto, ou através de uma classe.

Código: Selecionar todos

WITH OBJECT cnMySql
   :QueryCreate()
   :QueryAdd( "CODIGO",   mCodigo )
   :QueryAdd( "NOME",     mNome )
   :QueryAdd( "ENDERECO", mEndereco )
   :QueryExecuteInsert( "CLIENTES" )
Leitura

Código: Selecionar todos

WITH OBJECT cnMySql
   :cSql := "SELECT * FROM CLIENTES LIMIT 1"
   :Execute()
   mCodigo   := :Fields( "CODIGO" )
   mNome     := :Fields( "NOME" )
   mEndereco := :Fields( "ENDERECO" )
   :CloseRecordset()
ENDWITH

Padrão repository com xHarbour

Enviado: 11 Jan 2020 10:07
por bencz
Pois é... eu sei que o Harbour já fornece coisas mais interessantes... mas, como falei, foi apenas um teste!
Espero que sirva de estudos para alguém meu amigo! (:

Padrão repository com xHarbour

Enviado: 11 Jan 2020 10:18
por JoséQuintas
bencz escreveu:Pois é... eu sei que o Harbour já fornece coisas mais interessantes...
ADO é Microsoft, não é Harbour.
Pode ser usado no XHarbour também.

Padrão repository com xHarbour

Enviado: 11 Jan 2020 13:56
por bencz
Sim, eu sei que o ADO é da Microsoft...
O que quis dizer, é que o (x)Harbour já fornece ferramentas 'nativamente' para facilitar isso...
foi apenas uma maneira de dizer (: