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).
![Arquitetura multicamadas](http://arquivo.devmedia.com.br/artigos/Leandra-Mendes/Framework-OR-Delphi/Framework-OR-Delphi1.jpg)
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).
![Arquitetura multicamadas com camada de persistência de dados]( //arquivo.devmedia.com.br/artigos/Leandra-Mendes/Framework-OR-Delphi/Framework-OR-Delphi2.jpg)
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);
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).
![Exemplo da árvore do projeto](http://arquivo.devmedia.com.br/artigos/Leandra-Mendes/Framework-OR-Delphi/Framework-OR-Delphi3.jpg)
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.
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.
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.
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.
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.
![Data Module: srvModBaseDados](http://arquivo.devmedia.com.br/artigos/Leandra-Mendes/Framework-OR-Delphi/Framework-OR-Delphi4.jpg)
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.
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.
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).
![Interface demonstrando o uso da camada de persistência](http://arquivo.devmedia.com.br/artigos/Leandra-Mendes/Framework-OR-Delphi/Framework-OR-Delphi5.jpg)
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;
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.
- Granaty, Jones. Introdução ao Modelo Multicamadas. Acesso em 01 jul. 2013.
- Leonhardt, Rodrigo. RTTI (Run-time Type Information). Acesso em 01 jul. 2013.
- Guedes, José Mário Silva. Trabalhando com Atributos (Custom Attributes). Acesso em 01 jul. 2013.
- Mourão, Rodrigo Carreiro. DataSnap XE, Generics, RTTI e DAO. Acesso em 01 jul. 2013.
- Embarcadero, docwik. Overview of Generics. Acesso em 01 jul. 2013.