Artigo no estilo: Curso

Por que eu devo ler este artigo:Neste artigo iremos tratar de um tópico muito importante em um projeto orientado a objetos, que é a camada de acesso a dados. É mostrado como implementar classes DAO, onde temos o mapeamento das classes de negócio para tabelas no banco de dados e o mapeamento das propriedades para as colunas das tabelas criadas, e vice-versa. Veremos objetos sendo transformados em linhas na tabela e propriedades transformadas em colunas.

O Delphi, no que diz respeito a linguagem, implementa todos os requisitos de uma linguagem orientada a objetos, pois nos permite fazer abstração, encapsulamento, herança e polimorfismo. Porém, o que mais vemos nos desenvolvedores Delphi é o desenvolvimento de aplicações de forma estruturada, onde são explorados apenas os recursos RAD que o IDE proporciona.

Um dos grandes pontos negativos de trabalhar com Delphi é a falta de um framework de mapeamento objeto-relacional robusto e que seja apoiado pela Embarcadero.

Existem iniciativas como o DORM (open-source) e o Aurelius (comercial), mas são soluções que ainda não se tornaram populares. Neste artigo veremos uma solução manual para resolver esse problema de mapeamento objeto-relacional.

Existem boas práticas, como a Inversão de Controle (BOX 1), que no Delphi não possui um framework apoiado e incentivado pela Embarcadero.

Apesar da ferramenta não oferecer um framework nativo para trabalharmos com orientado a objetos, a Delphi Language possui todos os recursos necessários para desenvolvermos orientado a objetos, como Generics e Métodos Anônimos.

BOX 1. Inversão de Controle e Injeção de Dependência

Este princípio é a base para qualquer bom design de software orientado a objetos. O princípio da Inversão de Dependência nos diz que módulos de alto nível não devem ser dependentes de módulos de baixo nível, ambos devem depender de abstrações, que por sua vez, não devem depender de detalhes.

Inverter a dependência faz com que o cliente não fique frágil a mudanças relacionadas a detalhes de implementação, isto é, mudar um detalhe da implementação não faz com que sejam necessárias alterações no cliente.

Este princípio é bastante presente em muitos padrões de projeto, pois a maioria deles definem uma interface para que não haja dependências de implementações.

Outro padrão que geralmente anda junto com o princípio de inversão de dependência é a Injeção de Dependências, que é uma forma de conseguir a inversão de controle. Nesta solução, as dependências entre os módulos não são definidas programaticamente, mas sim pela configuração de uma infraestrutura de software (container) que é responsável por injetar em cada classe suas dependências declaradas.

O padrão de Injeção de dependências sugere que uma conexão com banco de dados seja injetada na classe. Com isso, além de inverter a dependência, a classe não precisa se preocupar com o ciclo de vida das suas dependências (no exemplo da conexão, a classe não precisa abrir ou fechar conexão, apenas receber uma referência desta conexão e a utiliza).

Impedância Objeto Relacional

A Orientação a Objetos traz como principal vantagem sobre a estruturada a representação de objetos de maneira bastante semelhante ao mundo real, o que torna-se um desafio a mais quando precisamos fazer a persistência destes nos bancos de dados relacionais.

Existem soluções de bancos de dados orientados a objetos, mas estes ainda não se tornaram populares, sendo os bancos de dados relacionais ainda muito mais utilizado que estes.

Não existe uma conversão direta entre os objetos que construímos nas linguagens de programação e o banco de dados relacionais, por isso é necessário criarmos o que é chamado de Mapeamento Objeto-Relacional, onde objetos são transformados geralmente em registros em uma tabela e vice-versa.

Mapeamento Objeto Relacional

Isso surgiu porque precisamos salvar o estado de um objeto (atributos, herança, polimorfismo) em tabelas do banco de dados. O mapeamento básico pode ser feito através de conversões manuais ou através de frameworks de persistência, onde ambas possuem vantagens e desvantagens, mas a conversão manual nos dá uma melhor performance, enquanto que com frameworks de persistência não precisamos nos preocupar com escrita de SQL.

Em Delphi os frameworks de mapeamento objeto relacional geralmente nos possibilitam a configuração de persistência através de arquivos XML ou o recurso de annotations ou custom atributes.

No mapeamento objeto relacional básico o que temos é o mapeamento de classes para uma tabela do banco de dados, temos também o mapeamento de objetos da classe para registros da tabela e as propriedades da classe para colunas da tabela.

Padrão DAO (Data Access Object)

O padrão de projeto DAO surgiu com a necessidade de separarmos a lógica de negócios da lógica de persistência de dados.

Este padrão permite que possamos mudar a forma de persistência sem que isso influencie em nada na lógica de negócio, além de tornar nossas classes mais legíveis.

Classes DAO são responsáveis por trocar informações com o SGBD e fornecer operações CRUD e de pesquisas, além de ser capaz de buscar dados no banco e transformar em objetos ou lista desses através de listas genéricas.

Também deverá receber os objetos, converter em instruções SQL e mandar para o bando de dados. Toda interação com a base se dará através destas classes, nunca das classes de negócio, muito menos de formulários.

Se aplicarmos este padrão corretamente será abstraído completamente o modo de busca e gravação dos dados, tornando isso transparente para aplicação, facilitando muito na hora de fazermos manutenção na aplicação ou migração de banco de dados.

Também conseguimos centralizar a troca de dados com o SGBD, assim teremos um ponto único de acesso a dados e nossa aplicação um ótimo design orientado a objeto.

Generics

Generics é um recurso que foi incorporado na versão 2009 do Delphi, que permite criar estruturas genéricas. Na orientação a objetos podemos definir o tipo de retorno de uma lista de objetos e somente serão permitidas inserções de objetos daquele tipo, do contrário ocorrerá um erro ainda em tempo de compilação, assim, evitando erros em tempo de execução do tipo Invalid Type Casting, muito comum para quem trabalhava com listas de objetos em versões anteriores a 2009 em Delphi.

Para fazer uso de Generics temos que declarar o namespace System.Generics.Collections na seção uses do código.

As duas principais classes deste namespace são a TList<T> e TobjectList<T>.

A Classe TList<T> possibilita guardar coleções de qualquer tipo de dados, tanto primitivos quanto objetos. Já a classe TObjectList<T>, que é uma especialização de TList, pode armazenar somente objetos.

Ela tem a vantagem de que no momento que liberarmos sua instância da memória, todos os objetos contidos nela também são liberados, diferentemente das instâncias de TList, onde no caso de as usarmos para armazenar objetos, precisamos liberar cada objeto individualmente da memória.

FireDAC

FireDAC é a versão da Embarcadero para o AnyDAC, que foi adquirida e integrada ao Delphi e o C++ Builder. No Delphi o FireDAC está disponível na versão XE3 e XE4 com um conjunto de componentes extras, e a partir da versão XE5 vem na instalação padrão.

É um conjunto de componentes de alta performance, de fácil utilização e provê conexão com vários bancos de dados, tanto locais quanto coorporativos.

Cada banco de dados possui um driver e trabalhado de forma diferente. Os drivers do FireDAC são nativos para cada banco de dados e ainda possui pontes para ODBC e dbExpress. Vários bancos de dados são suportados, entre eles temos: MySQL, SQL Server, Oracle e SQLite.

Para quem tem projetos multicamadas com Delphi e DataSnap (BOX 2), ou também servidores REST, basta migrar a parte Server de DBExpress para FireDAC, pois o TFDQuery é um TDataSet, portanto compatível com o TDataSetProvider e TClientDataSet.

Na parte Client, continua o TClientDataSet sem necessidade de qualquer alteração. Tem suporte a Firemonkey e VCL e possui um conjunto de componentes visíveis e não-visíveis, DataSets, Adapters.

BOX 2. DataSnap

ODatasnapé o mecanismo desenvolvimento multicamadas do Delphi.Servidoresde aplicaçãoDatasnappodem ser desenvolvidos tanto em Delphi quanto C++Builder, porém a grande vantagem está no cliente, que pode ser desenvolvido em qualquer linguagem de programação que tenha suporte a JSON.

Em 2009 foi criado um novo driver para oDBExpress, mas ao invés deste driver conectar-se a um banco de dados qualquer, ele se conectaria a um servidor de aplicação.

Assim, a partir do Delphi 2009 a conexão do cliente com o servidor de aplicação passou a ser feita utilizandoDBExpresstrafegando os dados utilizando o protocolo TCP/IP.

Classe Base DAO

Abra o projeto no Delphi para continuarmos. Para facilitar o trabalho com as classes DAO, criamos uma classe base a qual será herdada por todas as classes DAO que irão interagir com o banco de dados, conforme pode ser visto na Listagem 1.

Listagem 1. Classe base TDAO


  01 unit DAO.Base;
  02 interface
  03 uses
  04   DAO.ConnectionFactory, FireDAC.Comp.Client;
  05 type
  06   TDAO = class
  07   protected
  08     Connection: TFDConnection;
  09     function GetKeyValue(ATable: string): Integer;
  10   public
  11     constructor Create;
  12     destructor Destroy; override;
  13   end;
  14 implementation
  15 { TDAO }
  16 constructor TDAO.Create;
  17 begin
  18   Connection := TConnectionFactory.GetConnection;
  19 end;
  20 destructor TDAO.Destroy;
  21 begin
  22   Connection.Free;
  23   inherited;
  24 end;
  25 function TDAO.GetKeyValue(ATable, AColumn: string): Integer;
  26 var
  27   SQL: string;
  28   Id: Integer;
  29 begin
  30   SQL := 'select coalesce(max(‘ + AColumn + ‘),0) + 1 from ' + ATable;
  31   Id := Integer(Connection.ExecSQLScalar(SQL));
  32   result := Id;
  33 end;
  34 end.

Nesta classe temos a declaração de uma referência para TFDConnection com escopo protegido, de maneira que possamos acessar este atributo em todas as classes derivadas desta. Usaremos este objeto em todas as classes descendentes, tanto para executar comandos de consulta quanto comandos de atualização no banco de dados.

Temos também o método GetKeyValue, que deverá ser chamado toda vez que precisarmos gerar uma nova chave para alguma tabela, bastando passar por parâmetro o nome e a coluna que desejamos receber a chave.

Utilizamos o método ExecSQLScalar do FireDAC para buscarmos o valor desejado. Este deve ser usado sempre que nossa consulta retornar apenas uma informação. O seu retorno é do tipo variant, por isso fizemos um typecast antes de fazermos atribuição à variável Id.

O mecanismo de geração da chave que escolhemos foi o de pegar o máximo valor existente na coluna da tabela mais um, porém poderíamos usar qualquer outro mecanismo, como o uso de Generators do Firebird, por exemplo.

Toda comunicação com o banco de dados será feita através de um objeto da classe TFDConnection que será disponibilizado pela classe TConnectionFactory, como mostra a Listagem 2.

Listagem 2. Relembrando a classe TConnectionFactory

Quer ler esse conteúdo completo? Tenha acesso completo