Padrão repository com xHarbour
Enviado: 10 Jan 2020 16:29
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
connectionFactory.prg
dataaccess.prg
Modelos\Usuario.prg
repository\UsuariosRepository.prg
A tabela de testes que utilizei no MS-SQL é:
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....
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
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
#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
#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
#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
#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 é:
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....