Olá
Quanto tempo hein? O último post data de 18/10/2014. Muita coisa aconteceu desde então, principalmente com o nosso amado e sofrido país. Mas, mantemos a esperança de dias melhores.
Bem, estou dando uma passada rápida aqui para dar uma atualizada nos fontes do projeto: GitHub ORM Básico em Delphi
Sim, utilizo este projeto em meus sistemas. Verdade! O porquê de não utilizar um Framework pronto já foi explicado no início desta série. Em resumo, gosto de ter controle do meu trabalho e me ajuda a evoluir como desenvolvedor (pelo menos tento).
O tempo todo eu me cobro: pense em interface e não em implementação. Quando volto a olhar meus códigos antigos, sempre me deparo com desenvolvimento para implementação. Pessoal, é inevitável: reabro todos os diagramas e volto a repensar todos os passos adotados e onde foi que errei. Se é que me entendem?!?
Desde o último post, venho ajustando alguns detalhes e implementando novas funcionalidades, à medida que a necessidade surge. Recentemente, voltei a analisar o diagrama base deste projeto, que segue:
Foquemos na classe TDaoIBX. Ela herda de TDaoBase, que é uma classe abstrata que implementa alguns métodos. Apesar de ser uma classe, entendemos que atua como uma interface, visto que é ela quem dita as ações principais das classes filhas e não será instanciada diretamente. Porém, ainda não estou satisfeito.
Além disso, TDaoIBX tem muitas responsabilidades o que me incomoda sobremaneira. Isto ficou ainda mais evidente quando precisei inserir um novo DAO no projeto baseado no FireDac. Novamente, caí na questão de como instanciar nosso DAO. Não queremos ter trabalho quando mudar de suíte de componentes. Para um novo projeto, resolvi mudar de IBX para Firedac e, como vínhamos pensando nisso desde o início, não tive maiores problemas. Na verdade, não tive problema algum! Porém, a instanciação voltou a me incomodar. Por vezes, precisamos dos componentes como TIBDatabase ou TFDConnection inseridos num datamodule por um ou outro motivo.
Outro ponto: no título deste post citei algumas manias. Eu que venho de Access (kkk sim, é verdade!), Clipper, Visual Basic, etc., tenho uma mania, ou seria preguiça, ou sei lá o quê!, de trabalhar procedural. Como isso me aflige! (Percebam que hoje a autocrítica tá pegando^^).
Existe em nosso projeto uma unit chamada PrsAtributos. É dela que vem algumas classes para nossos atributos, como(vejam no digrama acima): AttPK, AttTabela, AttMaxValue, etc. Nesta mesma unit, temos algumas funções para pegar nome da tabela, validar tabela, pegar chaves primárias… E é aí, meus caros, a que me refiro programar procedural, pois não deixa de ser isso.
Mãos à obra!
- As funções de gerar os SQL definidos em TDaoBase, que são virtuais, não é bem uma função DAO. Outro detalhe é que, principalmente nos selects, quando formos utilizar algum banco de dados que saia do padrão SQL, teremos que sobrescrever estes métodos em alguma nova classe filha;
- Os métodos para setar parâmetros e valores dos campos merecem também uma melhor análise;
- As classes DAO filhas (IBX, FireDac, …) são definidas para trabalhar especificamente com determinada suíte de componentes de acesso a dados, portanto a criação de uma classe para trabalhar as transações tornou-se desnecessária. Podemos simplificar;
- Necessitaremos criar uma classe responsável por processar os atributos;
- Após retirarmos o que é desnecessário de TDaoBase, ela se torna verdadeiramente uma interface, sem métodos implementados. Então, por que não utilizar uma interface?
Após alterações, temos:
- Temos agora uma interface para a geração do SQL e uma classe que a implementa, a TPadraoSql. Ela será utilizada por padrão. Não resolvemos ainda a questão do banco utilizar uma instrução SQL diferente, mas já deixamos pavimentado o processo. Numa futura alteração, bastaria passar por injeção de dependência no ato da instanciação do nosso DAO (teremos apenas que alterar o construtor para receber a interface), um outro padrão que implemente IBaseSql como, por exemplo, TOracleSql. É só um exemplo pessoal, não estou dizendo que Oracle sai do padrão ou não. É que cada banco de dados tem algumas pequenas diferenças, exemplo: buscar determinado número de registro num “select”;
- Foi criado uma nova interface para tratar os parâmetros e valores dos campos, a IQueryParams. Cada DAO terá uma classe que implementará esta interface. No diagrama acima mostrei apenas a classe do DAO Firedac, mas a IBX também tem sua classe para este fim;
- Não existe mais classe para transação. O próprio DAO delega esta função diretamente para quem é responsável por isso, ou seja, o componente da suíte que trata desta área. Olhe para cada DAO (TDaoIBX e TDAOFireDac). Tem um field para isto, o FTransacao.
- A classe TAtributos é a classe que irá ser responsável por trabalhar os atributos das tabelas.
- TDaoBase dá lugar a IDaoBase. Isso abre um leque de novas possibilidades ;).
Bom, algum desavisado, quando visualizar estes diagramas pela primeira vez, pode vir a estranhar nos DAOs os vários métodos repetidos. São apenas overloads. Por exemplo, no método Excluir, podemos utilizar a chave primária ou então passar o campo o qual utilizaremos para o filtro da exclusão. Inserir, podemos passar o(s) campos(s) a ser(em) ignorado(s) (principalmente quando é referente a uma chave estrangeira – null – ou campos que não serão utilizados). E assim por diante.
Mencionei que TDaoBase estava com responsabilidades demais. E se observamos, mesmo depois da alteração, vemos que os métodos para tratar transação ainda estão ali, porém, não são as classes DAOs que implementam, elas delegaram isso para os componentes (encapsularam). Assim, TDaoIBX não sabe como foi implementado um StartTransaction, um Commit e nem RollBack. Quem faz isso é o field FTransaction, que é do tipo TIBTransaction. Entenderam? Eu simplifiquei. Em vez de criar um objeto à parte para tratar transações, o próprio Dao chama os comandos que foram delegados a quem realmente faz o processo. Ou seja, em vez de:
[sourcecode language=”Delphi”]
Transacao := Transacao.create;
…
Transacao.StartTransaction;
…
Transacao.Commit;
…
Transacao.Rollback;
…
Transacao.free;
[/sourcecode]
Faremos assim:
[sourcecode language=”Delphi”]
Dao.StartTransaction;
…
Dao.Commit;
…
Dao.Rollback;
[/sourcecode]
Não importa como Dao irá iniciar, comitar e cancelar uma transação. O que importa é que isso realmente seja feito.
Só por curiosidade, um método novo (Limpar), serve apenas para “zerar” os dados setados no objeto da classe TTabela passada no parâmetro. Não é para deletar dados do banco de dados.
A classe TAtributos, apesar de parecer, não é um Singleton! Por que da afirmação? Simples, olhando para a sua utilização poderia surgir a dúvida, pois, podemos utilizá-lo de duas formas:
[sourcecode language=”Delphi”]
var
Atributos: IAtributos;
begin
Atributos := TAtributos.create;
Atributos.ValidaTabela(….
[/sourcecode]
e
[sourcecode language=”Delphi”]
TAtributos.Get.ValidaTabela(…
[/sourcecode]
Porém, notem na implementação de TAtributos que sempre teremos uma nova instância.
Bom é isso! Como se passou muito tempo, creio que poucos ainda utilizam alguma coisa aqui implementada, mas serve para aprendizado. Principalmente para mim, que ao postar sou forçado a ficar revendo o projeto.
Infelizmente, não tem como voltar a postar como antes, mas sempre que for possível e efetuar um bloco de atualizações no github, estarei informando.
Abraços!
Valeu. Parabéns.