Criando seu próprio framework de persistência Objeto-Relacional no Delphi XE

Veja neste artigo como criar seu próprio framework de persistência Objeto-Relacional no Delphi XE.

Veja neste artigo como criar um framework de persistência em Delphi.

Para o desenvolvimento de softwares, atualmente, a arquitetura mais disseminada é a multicamadas. Geralmente divida em três camadas: banco de dados, camada de negócios e interface (apresentação) (Figura 1).

Figura 1. Arquitetura multicamadas

O fator complicador nesse modelo de arquitetura está no acesso aos dados que é realizada pela camada de negócios através do acesso nativo a camada de dados. Para minimizar esta situação, nos deparamos com a necessidade de intercalar nessas camadas uma camada de persistência de dados (Figura 2).

Figura 2. Arquitetura multicamadas com camada de persistência de dados

As operações básicas da camada de persistência são conhecidas como operações de CRUD (Create, Retrieve (ou Read), Update e Delete). Assim sendo, a camada de persistência deve realizar estas operações de forma transparente a aplicação e conseqüentemente livrar a camada de negócios das preocupações relativas ao acesso a dados.

Portanto, o objetivo deste artigo é justamente mostrar como os desenvolvedores que trabalham com arquitetura multicamadas, podem criar o seu próprio framework de persistência objeto-relacional em Delphi XE, levando suas aplicações a um nível de abstração de dados necessário a produtividade do desenvolvimento.

Criando as Classes de Entidades

Quando se pensa em camada de persistência primeiramente se pensa em criar as classes de entidades. E quem seriam estas classes? Seriam exatamente espelhos das entidades, ou tabelas, do esquema da base de dados usado na aplicação. Para ilustrar a criação do framework de persistência objeto-relacional, primeiramente será necessário criar uma tabela no Firebird (Listagem 1).

CREATE TABLE TB_CAD_ALUNO ( MATRICULA VARCHAR(10) NOT NULL, NOME_ALUNO VARCHAR(100) NOT NULL, ENDERECO VARCHAR(200), TELEFONE VARCHAR(20), CPF VARCHAR(11) ); ALTER TABLE TB_CAD_ALUNO ADD CONSTRAINT TB_CAD_ALUNO_PK PRIMARY KEY (MATRICULA);
Listagem 1. Script de criação da tabela TB_CAD_ALUNO

Depois de criada a tabela, cria-se um novo projeto no Delphi XE para começar a implementar os artefatos necessários a camada de persistência. Assim sendo:

Crie um novo projeto no Delphi XE do tipo VCL Forms Applications. Salve o projeto. Crie junto com os fontes deste projeto uma pasta chamada Framework. Esta será a pasta onde os fontes necessários ao Framework de persistência serão arquivados, objeto de estudo deste artigo (Figura 3).

Figura 3. Exemplo da árvore do projeto

Usa-se o conceito de RTTI (Run-time Type Information) [2] para criar as classes de entidades. Assim sendo, adicione uma nova Unit ao seu projeto e dê a ela o nome de TEntity.pas, salve-a dentro da pasta Framework e implemente-a de acordo com a Listagem 2. Esta classe será a classe genérica para qualquer classe de entidade presente em qualquer esquema de banco de dados.

unit TEntity; interface Uses RTTI; type TGenericEntity = class(TObject) end; implementation end.
Listagem 2. TEntity.pas

Agora crie a classe de entidade referente à tabela TB_CAD_ALUNO (Listagem 1). Para tal crie uma nova Unit e salve-a dentro da pasta Framework como tblAluno.pas e implemente-a de acordo com a Listagem 3.

unit tblAluno; interface Uses TEntity; type //nome da classe de entidade TAluno = class(TGenericEntity) private //colocar aqui todas as propriedades privadas da classe de acordo com //os campos da tabela FMatricula:string; FNomeAluno:string; FEndereco:string; FTelefone:string; FCPF:string; //colocar aqui todos os métodos sets para cada uma das propriedades acima procedure setFMatricula(value:string); procedure setFNomeAluno(value:string); procedure setFEndereco(value:string); procedure setFTelefone(value:string); procedure setFCPF(value:string); public //colocar aqui todas as propriedades públicas de acesso aos objetos dessa //classe property Matricula: string read FMatricula write setFMatricula; property Nome:string read FNomeAluno write setFNomeAluno; property Endereco:string read FEndereco write setFEndereco; property Telefone:string read FTelefone write setFTelefone; property CPF:string read FCPF write setFCPF; function ToString:string; override; end; implementation procedure TAluno.setFMatricula(value:string); begin FMatricula:= value; end; procedure TAluno.setFNomeAluno(value:string); begin FNomeAluno:= value; end; procedure TAluno.setFEndereco(value:string); begin FEndereco:= value; end; procedure TAluno.setFTelefone(value:string); begin FTelefone:= value; end; procedure TAluno.setFCPF(value:string); begin FCPF:= value; end; function TAluno.toString; begin result := '''' Matricula: ''''+ Matricula + '''' Nome: ''''+ Nome + '''' Endereco: '''' + Endereco + '''' Fone: '''' + Telefone + '''' CPF: '''' + CPF; end; end.
Listagem 3. TblAluno.pas

Agora é necessário customizar a classe TAluno usando conceitos de Custom Attributes [3]. O que será realizado é simplesmente colocar Custom Attributes para cada elemento da classe TAluno de forma que seja possível tirar proveito deles através da RTTI e criar o DAO (Data Access Object).

Primeiro crie os Custom Attributes necessários para identificar dentro da classe de entidade a tabela do banco de dados e seus campos. Crie, portanto, uma nova Unit, chamada de CAtribEntity e salve-a dentro de Framework, implementando-a conforme a Listagem 4.

unit CAtribEntity; interface //atributo para determinar o nome da tabela na entidade a ser usada type //nome da tabela TableName = class(TCustomAttribute) private FName: String; public constructor Create(aName: String); property Name: String read FName write FName; end; type //determinar se o campo é um campo chave KeyField = class(TCustomAttribute) private FName: String; public constructor Create(aName: String); property Name: String read FName write FName; end; type //nome do campo na tabela FieldName = class(TCustomAttribute) private FName: String; public constructor Create(aName: String); property Name: String read FName write FName; end; implementation constructor TableName.Create(aName: String); begin FName := aName end; constructor KeyField.Create(aName: String); begin FName := aName; end; constructor FieldName.Create(aName: String); begin FName := aName; end; end.
Listagem 4. CAtribEntity.pas

Depois customize a classe de entidade TAluno com os Custom Attributes necessários, alterando a implementação da classe conforme a Listagem 5. Dessa forma, tem-se neste exato momento o mapeamento objeto-relacional usando para isso os Custom Attributes.

unit tblAluno; interface Uses TEntity, CAtribEntity; type //nome da classe de entidade [TableName(''''TB_CAD_ALUNO'''')] TAluno = class(TGenericEntity) private //colocar aqui todas as propriedades privadas da classe de acordo com //os campos da tabela FMatricula:string; FNomeAluno:string; FEndereco:string; FTelefone:string; FCPF:string; //colocar aqui todos os métodos sets para cada uma das propriedades acima procedure setFMatricula(value:string); procedure setFNomeAluno(value:string); procedure setFEndereco(value:string); procedure setFTelefone(value:string); procedure setFCPF(value:string); public //colocar aqui todas as propriedades públicas de acesso aos objetos dessa //classe [KeyField(''''MATRICULA'''')] [FieldName(''''MATRICULA'''')] property Matricula: string read FMatricula write setFMatricula; [FieldName(''''NOME_ALUNO'''')] property Nome:string read FNomeAluno write setFNomeAluno; [FieldName(''''ENDERECO'''')] property Endereco:string read FEndereco write setFEndereco; [FieldName(''''TELEFONE'''')] property Telefone:string read FTelefone write setFTelefone; [FieldName(''''CPF'''')] property CPF:string read FCPF write setFCPF; function ToString:string; override; end; implementation procedure TAluno.setFMatricula(value:string); begin FMatricula:= value; end; procedure TAluno.setFNomeAluno(value:string); begin FNomeAluno:= value; end; procedure TAluno.setFEndereco(value:string); begin FEndereco:= value; end; procedure TAluno.setFTelefone(value:string); begin FTelefone:= value; end; procedure TAluno.setFCPF(value:string); begin FCPF:= value; end; function TAluno.toString; begin result := '''' Matricula: ''''+ Matricula + '''' Nome: ''''+ Nome + '''' Endereco: '''' + Endereco + '''' Fone: '''' + Telefone + '''' CPF: '''' + CPF; end; end.
Listagem 5. tblAluno.pas – alteração usando os Custom Attributes

Criando o DAO

DAO (Data Access Object) é um padrão de desenvolvimento de softwares que trata da persistência de objetos separando a camada de dados da camada de negócios [4]. Para entender melhor a implementação do DAO é necessário conhecer os conceitos de Generics [4, 5]. Este recurso nos permite trabalhar com uma estrutura genérica, o que faz muita diferença em linguagens fortemente tipada como o Delphi, podendo definir-se uma estrutura genérica customizada, ou melhor, tipada de acordo com a necessidade.

Primeiro crie um DataModule com o nome de srvModBaseDados e salve também dentro da pasta Framework. Este DataModule será responsável por fazer a interface entre a camada de dados e o DAO da camada de persistência, ou seja, neste ponto já se cria a primeira independência de projeto em relação ao SGBD utilizado. Coloque TDSServerModuleBaseDados na propriedade name do DataModule. Veja na Figura 4 os componentes visuais necessários ao DataModule.

Figura 4. Data Module: srvModBaseDados

Configure o componente TSQLConnection para acesso ao banco de dados criado neste artigo. E depois configure o Componente TSQLDataSet para o respectivo TSQLConnection do DataModule. Depois crie os métodos necessários à conexão com o banco e os métodos necessários ao CRUD (Listagem 6).

unit srvModBaseDados; interface uses SysUtils, Classes, DBXFirebird, DBXPool, FMTBcd, DB, SqlExpr; type TDSServerModuleBaseDados = class(TDataModule) LSCONEXAO: TSQLConnection; SQLDSServidor: TSQLDataSet; private { Private declarations } public { Public declarations } //funções para o banco de dados function Conectar:boolean; function Desconectar:boolean; //funções para manipular as entidades function getDataSet(strQry:string): TDataSet; function execSql(strQry:string): boolean; end; var DSServerModuleBaseDados: TDSServerModuleBaseDados; implementation {$R *.dfm} //funções para o banco de dados function TDSServerModuleBaseDados.Conectar:boolean; begin try LSCONEXAO.Connected := true; result := true; except result := false; end; end; function TDSServerModuleBaseDados.Desconectar:boolean; begin try LSCONEXAO.Connected := false; result := true; except result := false; end; end; //funções para manipular as entidades function TDSServerModuleBaseDados.getDataSet(strQry:string): TDataSet; begin SQLDSServidor.Close; SQLDSServidor.Params.Clear; SQLDSServidor.CommandType := ctQuery; SQLDSServidor.CommandText := strQry; SQLDSServidor.Open; result := SQLDSServidor; end; function TDSServerModuleBaseDados.execSql(strQry:string): boolean; Var msgErro:string; begin result := false; SQLDSServidor.Close; SQLDSServidor.Params.Clear; SQLDSServidor.CommandType := ctQuery; SQLDSServidor.CommandText := strQry; try SQLDSServidor.ExecSQL; result := true; except on e: Exception do begin raise Exception.Create(e.Message) end; end; end; end.
Listagem 6. srvModBaseDados.pas

Assim sendo, crie outra Unit ao seu projeto, salvando também dentro de Framework com o nome de GenericDao. A Unit GenericDao consolida todos os conceitos de Generics, Rtti, DAO para que seja possível manipular determinada classe de entidade fazendo o mapeamento objeto-relacional (Listagem 7).

unit GenericDao; interface Uses Db, Rtti, CAtribEntity, TypInfo, SysUtils, srvModBaseDados; type TGenericDAO = class private class function GetTableName<T: class>(Obj: T): String; public //procedimentos para o crud class function Insert<T: class>(Obj: T):boolean; class function GetAll<T: class>(Obj: T): TDataSet; end; implementation class function TGenericDAO.GetTableName<T>(Obj: T): String; var Contexto: TRttiContext; TypObj: TRttiType; Atributo: TCustomAttribute; strTable: String; begin Contexto := TRttiContext.Create; TypObj := Contexto.GetType(TObject(Obj).ClassInfo); for Atributo in TypObj.GetAttributes do begin if Atributo is TableName then Exit(TableName(Atributo).Name); end; end; //funções para manipular as entidades class function TGenericDAO.Insert<T>(Obj: T):boolean; var Contexto: TRttiContext; TypObj: TRttiType; Prop: TRttiProperty; strInsert, strFields, strValues: String; Atributo: TCustomAttribute; begin strInsert := ''''''''; strFields := ''''''''; strValues := ''''''''; strInsert := ''''INSERT INTO '''' + GetTableName(Obj); Contexto := TRttiContext.Create; TypObj := Contexto.GetType(TObject(Obj).ClassInfo); for Prop in TypObj.GetProperties do begin for Atributo in Prop.GetAttributes do begin if Atributo is FieldName then begin strFields := strFields + FieldName(Atributo).Name + '''',''''; case Prop.GetValue(TObject(Obj)).Kind of tkWChar, tkLString, tkWString, tkString, tkChar, tkUString: strValues := strValues + QuotedStr(Prop.GetValue(TObject(Obj)).AsString) + '''',''''; tkInteger, tkInt64: strValues := strValues + IntToStr(Prop.GetValue(TObject(Obj)).AsInteger) + '''',''''; tkFloat: strValues := strValues + FloatToStr(Prop.GetValue(TObject(Obj)).AsExtended) + '''',''''; else raise Exception.Create(''''Type not Supported''''); end; end; end; end; strFields := Copy(strFields, 1, Length(strFields) - 1); strValues := Copy(strValues, 1, Length(strValues) - 1); strInsert := strInsert + '''' ( '''' + strFields + '''' ) VALUES ( '''' + strValues + '''' )''''; result := DSServerModuleBaseDados.execSql(strInsert); end; class function TGenericDAO.GetAll<T>(Obj: T): TDataSet; begin result := DSServerModuleBaseDados.getDataSet(''''SELECT T1.* '''' + '''' FROM '''' + GetTableName(Obj) + '''' T1 '''' ); end; end.
Listagem 7. GenericDao.pas

Portanto, até este ponto o seu Framework de persistência está criado. No próximo tópico vamos integrá-lo a aplicação Delphi criada no inicio deste artigo. A pasta Framework poderá ser copiada para qualquer aplicativo e usada sem necessidades de reescrita de código, ou poderá ficar em área pública a todos os seus projetos.

Usando a Camada de Persistência

Agora que a camada de persistência está criada cria-se o aplicativo para usá-la. O projeto do aplicativo foi criado no início deste artigo, portanto, será construída uma interface simples apenas para mostrar o uso do framework (Figura 5).

Figura 5. Interface demonstrando o uso da camada de persistência

Veja a implementação dos botões "Inserir" e "GetAll" (Listagem 8).

procedure TForm5.btnInserirClick(Sender: TObject); Var aluno:TAluno; begin aluno := TAluno.Create(); aluno.Matricula := edMatricula.Text; aluno.Nome := edNome.Text; aluno.Endereco := edEndereco.Text; aluno.Telefone := edTelefone.Text; aluno.CPF := edCPF.Text; if TGenericDAO.Insert(aluno) then begin ShowMessage(''''Aluno inserido!''''); end else begin ShowMessage(''''Aluno não inserido!'''') end; end; procedure TForm5.btnGetAllClick(Sender: TObject); begin ClientDataSet1.Close; DataSetProvider1.DataSet := TGenericDAO.GetAll(TAluno.Create()); ClientDataSet1.Open; end;
Listagem 8. Implementação da chamada dos métodos da camada de persistência

Conclusão

Conclui-se portanto que os desenvolvedores devem estar atento as novas implementações e recursos disponíveis em seus ambientes de desenvolvimento. Isso torna o trabalho de produção de softwares não tão árduo e é notória a necessidade cada vez mais inerente desta área em promover as boas práticas no desenvolvimento de softwares.

Artigos relacionados