Fala ai Radizeiro e Radizeira, tudo bem com você?

Eu tenho observado constantemente programadores e donos de software house questionando com as mudanças nas legislações, clientes constantemente reclamando em demoras na entrega dos projetos e correções constantes.

Te falo uma coisa, quando você não tem um projeto organizado e estruturado você irá sofrer constantemente com isso.

Quando aplicamos as boas práticas e clean code, cada coisa com seu nome dizendo de onde é e para que ele é, você tem maior escalabilidade nos seus software.

Então não deixa de se organizar, vamos seguir nessa nossa série e você terá grande oportunidade de poder melhorar muito o seu software.

Agora com o nosso projeto com a conexão separada, com as entidades mapeadas do ORMBr, agora precisamos criar um DAO (Data Access Object – Objeto de acesso a dados).

Se você desconhece o que é um DAO, ele é um padrão de persistência de dados, onde tem a separação das regras de negócios das regras de acesso a banco de dados, implementadas utilizando a orientação a objetos, o que temos feito até agora dentro de nossa série.

Todas as funcionalidades tais como obter uma conexão, mapear objetos e tipos de dados, processo esse já feito quando mapeamos as entidades pelo ORMBr.

Quando você não tem um DAO genérico, você terá que criar um DAO para cada entidade mapeada do seu banco de dados, por exemplo, um DAO para a entidade de cliente, um DAO para a entidade de produto, e por aí vai.

Quando você começa a compreender o possibilidade de abstrair isso tudo você consegue reduzir muito os seus códigos, é aí que entra os Genérics.

Mas você deve está se perguntando, como eu faço um DAO genérico?

Uma camada de conexão genérica utilizando o ORM para que eu não venha ter que reescrever todas as operações, INSERT, DELETE, UPDATE, SELECT, seja usando o ORM ou não, assim como falei anteriormente, imagina ter que fazer essas operações para todas as suas tabelas?

Dessa forma usando um DAO genérico, iremos fazer uma vez só, simplesmente passamos uma classe para esse generics e pronto, já irá trabalhar para nós.

Vamos ver melhor como vamos fazer isso?

Primeira coisa que iremos fazer é criar nossa interface para o DAO.

Observe que criamos nossa unit de interface para DAO, mas criamos uma camada específica para ele, onde dentro do model criamos o DAO.

Lembra que falei em posts anteriores dessa nossa série, que para cada camada eu crio uma interface para que possa ser trabalhado a abstração?

Então, nesse ponto já temos nossa unit de interface, e agora vamos criar nossa interface para o nosso DAO.

    iModelDAO<T> = interface
        ['{8EED134A-874E-4B0E-A78F-99042423B027}']
        function Dataset(Value : TDataSource) : iModelDAO<T>; overload;
        function Dataset(Value : TDataSet) : iModelDAO<T>; overload;
        function Open : iModelDAO<T>;
        function ApplyUpdate : iModelDAO<T>;
        function Save : iModelDAO<T>;
        function _this : T;
        function _newThis : T;
    end;

Se você observar temos um mesmo método sobrecarregado, onde ele pode receber tanto um DataSource quando um DataSet.

Nessa interface temos algumas funções do ORMBr, esse DAO irá suportar tudo que for de CRUD do ORM.

Qualquer que for o ORM que você estiver utilizando, você terá que utilizar suas particularidades que você quiser exportar para a camada de visão, todos os métodos que são necessários do seu ORM você irá criar nessa interface, e lá na classe que a implementa dentro desse método que você criou irá realizar o que seu ORM propôs para ser feito.

É essa a ideia, o que seu ORM tiver, e você quer exportar para o mundo, você terá que colocar nessa interface.

Os métodos _This e _newThis é uma particularidade do ORMBr, o que acabei de falar, tudo que o seu ORM tiver de  particularidade é colocada nessa interface.

O ORMBr para persistir um objeto, através de objetos mesmo e não através de um Dataset, ele precisa que você crie uma nova instância do objeto em questão baseado no que você está usando.

Fazendo assim quando você chamar o _newThis ele irá pegar uma copia do objeto e irá passar para uma outra variável para que possamos trabalhar.

Essa é uma particularidade do ORMBr, pode ser que em seu outro ORM você não precise trabalhar dessa forma.

Agora vamos criar nossa classe de DAO.

Vamos implementar essa nossa classe.

    type
        TModelDAO = class (TInterfacedObject, iModelDAO<T>)
        private
        public
           constructor Create;
           destructor Destroy; override;
           class function New : iModelDAO<T>;
        end;

implementation

{ TModelDAO }

constructor TModelDAO.Create;
begin

end;

destructor TModelDAO.Destroy;
begin
    inherited;
end;

class function TModelDAO.New: iModelDAO<T>;
begin
    Result := Self.Create;
end;

Observe que essa nossa interface está aguardando um genérics, para que minha interface de genérica funcione, minha classe precisa receber um generic, ficando desta forma:

...
    TModelDAO<T : class, constructor> = class (TInterfacedObject, iModelDAO<T>);
...

Minha classe recebeu T, que é um genérics, do tipo class e ele possui um constructor, isso é uma diretiva do ORMBr, porque o genérics dele precisa ter essa restrição, essa constrains do genérics.

Nesse meu DAO genérico específico para o ORMBr, irei fazer implementar todas os métodos da interface.

Depois de todos os métodos da interface implementados nessa nossa classe, iremos prepará-la para trabalhar agora com o ORMBr.

Para isso devemos abrir o exemplo do ORMBr, para que você possa ver como ele trabalha e traduzir para a nossa classe.

Com o exemplo Firedac CRUD simples do ORMBr aberto no create do formulário iremos ver o que o ORMBr precisa ter para funcionar.

...
    private
        oConn: IDBConnection;
        oContainerClient: IContainerDataSet<T>;
...
procedure TForm3.FormCreate(Sender: TObject);
begin
    // Instância da class de conexão via FireDAC
    oConn := TFactoryFireDAC.Create(FDConnection1, dnSQLite);
    // Client
    oContainerClient := TContainerFDMemTable<Tclient>.Create(oConn, FDClient);
    oContainerClient.Open;
end;

Se observarmos ele precisa de um objeto que podemos ver chamado oConn que é do tipo IDBConnection que é uma interface do ORMBr, e ele precisa de um ContainerDataSet que recebe o genérics da classe, daquele entidade que foi mapeada do banco.

Eu copio esses dois atributos do exemplo do ORMBr e colo no private da minha classe DAO.

    TModelDAO<T : class, constructor> = class (TInterfacedObject, iModelDAO<T>);
    private
        oConn: IDBConnection;
        oContainer: IContainerDataSet<T>;
        FConexao : iModelConexao;
        FQuery : iModelQuery;
        FNewThis : T;

No create do nosso DAO genérico e iremos implementar o que é feito no exemplo do ORMBr. E fazemos algumas pequenas alterações para que ele possa ser genérico.

constructor TModelDAO<T>.Create;
begin
    FConexao := TModelConexaoFactory.New.Conexao;
    FQuery := TModelConexaoFactory.New.Query;
    oConn := TFactoryFireDAC.Create(FConexao.Connection, dnSQLite);
    oContainer := TContainerFDMemTable<T>.Create(oConn, FQuery.Query);
    FNewThis := nil;
end;

No código do exemplo do ORMBr o atributo oConn precisa de um FDConnection, então para a nossa classe DAO nós colocamos a nossa conexão, que em post anteriores nós criamos.

Observe que não trabalhamos diretamente com a classe de conexão e sim com a interface de conexão, iModelConexao, pois as boas práticas dizem que devemos trabalhar com a interface e não com a classe concreta, para que você possa reduzir o acoplamento e aumentar o nível de abstração do seu código.

No nosso constructor você pode ver usamos somente nossa Factory, viu como reduzimos o acoplamento, se não tivéssemos essa Factory iriamos precisar de duas uses, uma para conexão e outra para query.

Dessa forma abstrairmos o exemplo do ORMBr para dentro do nosso sistema usando as boas práticas e reduzindo o acoplamento.

Agora irei precisar implementar os métodos que eu preciso, por exemplo, implementar o ApplayUpdate do ORMBr.

function TModelDAO<T>.ApplyUpdate: iModelDAO<T>;
begin
    oConn.StartTransaction;
    oContainer.ApplyUpdates(0);
    oConn.Commit;
end;

Eu cheguei a esse ApplayUpdate com base no exemplo do ORMBr.

Então irei realizar todas as implementações dos métodos de nossa classe DAO, para que ele esteja pronta para trabalhar perfeitamente com o ORMBr e seus métodos.

type
    TModelDAO<T : class, constructor> = class (TInterfacedObject, iModelDAO<T>)
    private
        oConn: IDBConnection;
        oContainer: IContainerDataSet<T>;
        FConexao : iModelConexao;
        FQuery : iModelQuery;
        FNewThis : T;
    public
        constructor Create;
        destructor Destroy; override;
        class function New : iModelDA<T>;
        function Dataset(Value : TDataSource) : iModelDAO<T>; overload;
        function Dataset(Value : TDataSet) : iModelDAO<T>; overload;
        function Open : iModelDAO<T>;
        function ApplyUpdate : iModelDAO<T>;
        function Save : iModelDAO<T>;
        function _this : T
        function _newThis : T;
    end;

implementation

{TModelDAO }

function TModelDAO<T>.ApplyUpdate: iModelDAO<T>;
begin
    Result := Self;
    oContainer.ApplyUpdates(0);
end;

constructor TModelDAO<T>.Create;
begin
    FConexao := TModelConexaoFactory.New.Conexao;
    FQuery := TModelConexaoFactory.New.Query;
    oConn := TFactoryFireDAC.Create(FConexao.Connection, dnSQLite);
    oContainer := TContainerFDMemTable<T>.Create(oConn, FQuery.Query);
    FNewThis := nil;
end;

function TModelDAO<T>.Dataset(Value: TDataSource): iModelDAO<T>;
begin
    Result := Self;
    Value.DataSet := FQuery.Query;
end;

function TModelDAO<T>.Dataset(Value: TDataSet): iModelDAO<T>;
begin
    Result := Self;
    Value.Assign(FQuery.Query);
end;

destructor TModelDAO<T>.Destroy;
begin
    inherited;
end;

class function TModelDAO<T>.New: iModelDAO<T>;
begin
    Result := Self.Create;
end;

function TModelDAO<T>.Open: iModelDAO<T>;
begin
    Result := Self;
    oContainer.Open;
end;

function TModelDAO<T>.Save: iModelDAO<T>;
begin
    Result := Self;
    oContainer.Save(FNewThis);
    Self.ApplyUpdate;
    FNewThis := nil;
end;

function TModelDAO<T>._newThis: T;
begin
    if FNewThis = nil then
        FNewThis := oContainer.Current;
    Result := FNewThis;
end;

function TModelDAO<T>._this: T;
begin
     Result := oContainer.Current;
end;

Esse é o meu DAO genérico, talvez ele não esteja tão claro neste momento, mas quando formos para o controller ai sim você irá deslumbrar dessa maravilha que é trabalhar usando as boas práticas.

Neste treinamento você vai aprender a aplicar técnicas que darão maior escalabilidade em seus softwares criando uma estrutura de forma prática e dinâmica, aplicando os padrões de boas práticas e clean code, além de compreender como aplicar os padrões de persistência de dados sem a necessidade de criar scripts de banco de dados.

CLIQUE AQUI E SAIBA MAIS SOBRE O TREINAMENTO COMO IMPLEMENTAR ORM EM ARQUITETURA MVC