Sei sulla pagina 1di 17

Mapeamento Objeto-Relacional com TMS Aurelius

Facebook Twitter

(0) (0)

Veremos neste artigo como funciona e como utilizar o framework de Mapeamento


Objeto-Relacional TMS Aurelius, que permite lidar com bancos de dados aproveitando
os recursos da orientação a objetos.
Fique por dentro

Se você já tentou criar uma aplicação de banco de dados orientada a objetos no Delphi sabe que uma das tarefas mais custosas neste cenário está
ligada à persistência das informações. Isto porque, há uma impedância natural em salvar um objeto de negócio em uma tabela de um banco de dados
relacional. Visando atender situações como esta é que surge o TMS Aurelius, um framework de mapeamento objeto-relacional produzido pela TMS
Software, que simplifica todo este cenário, com muita robustez e ganho de produtividade.

Historicamente, o Delphi sempre foi reconhecido por sua excelência RAD, com componentes visuais prontos para uso, no mais tradicional
arrastar-e-soltar. Aliado a isso também é reconhecido pela facilidade com que provê a construção de aplicações de banco de dados. Neste
cenário, o uso de DataSets e controles data-aware (os famosos controles “DB” - DBEdit, DBGrid e cia) se combina a uma programação
estruturada bastante eficiente por meio de procedures e functions. Até este ponto, todos os recursos necessários são basicamente nativos,
providos pela própria ferramenta, numa instalação comum. Todavia, no cotidiano, este contexto pode seguir por outros direcionamentos, tal
qual é o caso da POO (Programação Orientada a Objetos).

A Programação Orientada a Objetos acaba por definir outro conceito de desenvolvimento, agora baseado em objetos relacionados ao mundo
real. Passando isso para o contexto das aplicações de banco de dados faz com que não se tenha mais simples DataSets manipulando
instruções SQL a serem enviadas ao banco de dados, mas sim a manipulação de efetivos objetos de negócio. Tendo em vista esta abordagem,
a estrutura provida pela aplicação, agora baseada em objetos, acaba por se tornar incompatível com a própria estrutura relacional do banco de
dados. Visando superar este tipo de barreira é que surgem os frameworks ORM (Object-Relational Mapping, ou Mapeamento Objeto-
Relacional, em português).

Em termos práticos, o que um framework ORM faz é interpretar os dados envolvidos, transformando então objetos de negócio em dados
relacionais, bem como o caminho inverso, onde as informações provindas de uma base de dados dão origem a estes objetos. Durante esta
jornada, as instruções SQL envolvidas acabam sendo suprimidas de qualquer contato direto com o desenvolvedor, dando origem a métodos
concretos que, no final das contas, simplificam todo o processo de codificação.

Dito isto, este artigo transpõe então todo este cenário ao contexto do Delphi, por meio da apresentação de uma solução bastante eficaz e
condizente com toda a praticidade e facilidade já conhecidos do IDE.

TMS Aurelius

O TMS Aurelius, ou simplesmente Aurelius, é um framework ORM exclusivo para Delphi, produzido pela empresa TMS Software. Sua
distribuição se dá de forma comercial, ou seja, há a necessidade da aquisição de uma licença para o seu uso em produção, porém o
framework desfruta de uma série de fatores e recursos que justificam a sua compra e plena utilização.

Licença

Em suma, o Aurelius é oferecido em três opções, que se diferenciam basicamente pela quantidade de usuários em uso:

Single Developer License: habilita a utilização para um único usuário (desenvolvedor);


Small Team: habilita a utilização para uma equipe pequena de até dois desenvolvedores;
Site License: habilita a utilização para um número irrestrito de usuários na empresa relacionada;
Para fins de testes e conhecimento, o framework dispõe de um período de avaliação (Trial), suficiente para comprovar toda a sua eficiência
no que ele se propõe a fazer.

Versões suportadas do Delphi

Uma tendência bastante positiva do Aurelius é o seu suporte atualizado às mais recentes versões do Delphi, o que significa dizer que muito
provavelmente você terá uma nova versão disponível sempre que um novo release do IDE for lançado. De forma oficial, o suporte do
framework se inicia no Delphi 2010, passando pela família XE (XE, XE2, XE3, etc.), Delphi 10 Seattle, até o recente Delphi 10.1 Berlin (até
o momento da escrita deste artigo).

Componentes

Um cenário bastante comum no contexto Delphi é a aquisição de uma biblioteca de componentes que, uma vez instalada, disponibiliza na
Tool Palette do IDE uma série de elementos prontos para uso pelo simples arrastar-e-soltar em um Form ou Data Module. Com o TMS
Aurelius isso foge um pouco do tradicional, uma vez que sua instalação provê apenas um único elemento na paleta de componentes do
Delphi (Figura 1). Isto porque, toda sua essência se dá por meio de sua estrutura de classes e interfaces provenientes de seu framework.
Assim como veremos mais adiante, o uso do Aurelius em uma aplicação Delphi se dá essencialmente por meio de código, o que torna o
processo bastante intuitivo com o passar do tempo.

Figura 1. Componente TAureliusDataset

Por que utilizar o TMS Aurelius?

As razões para se utilizar o Aurelius podem ser inúmeras e variáveis, tudo irá depender do contexto ao qual estará inserido. Falando de forma
mais abrangente, o framework em si se baseia em três pilares que estabelecem os principais benefícios que uma aplicação irá obter com o seu
uso: produtividade, manutenibilidade e portabilidade.

Ganho em produtividade

O termo produtividade diz respeito à forma simples com que o Aurelius lida com a manipulação de dados. Neste aspecto, toda a codificação
da aplicação será voltada diretamente a objetos, e não mais a instruções SQL. De forma ilustrativa, na Listagem 1 é exibido um trecho de
código tradicional, utilizando FireDAC para a seleção de um determinado registro da tabela CUSTOMER do banco Employee do Firebird.
Na mesma medida, na sequência é exibido como ficaria esta mesma instrução utilizando-se o Aurelius.

Listagem 1. Trecho tradicional utilizando FireDAC

// forma tradicional FDQuery1.SQL.Clear;

FDQuery1.SQL.Add('select CUST_NO, CUSTOMER, PHONE_NO, CITY, STATE_PROVINCE,

COUNTRY from CUSTOMER where CUST_NO = :CUST_NO');

FDQuery1.ParamByName('CUST_NO').AsInteger := 1001;

FDQuery1.Open;

// usando TMS Aurelius

Customer := Manager1.Find<TCustomer>(1001);

Além de um código mais enxuto, outra vantagem disso é que por se tratar de objetos Delphi, qualquer erro relacionado à sintaxe será
verificado ainda em design-time pelo compilador, ao contrário de uma instrução SQL na forma de string, que só será verificada em runtime.
Manutenção de código

O Aurelius acaba por abstrair todo o conhecimento sobre a camada de acesso a dados e instruções SQL envolvidas na aplicação. Assim, o
desenvolvedor volta seu foco apenas ao uso da estrutura provida pelo framework, o que tende a simplificar diversos cenários, facilitando em
termos de manutenção. Prova disso é o próprio exemplo anterior que mostra como uma codificação baseada no Aurelius torna-se muito mais
enxuta. Outra menção que pode ser feita aqui é que, independente do banco de dados a ser atendido (se Firebird, Oracle, MySQL ou outro),
uma mesma codificação será utilizada, ao contrário dum cenário mais tradicional com DataSets, onde teríamos instruções SQL específicas a
cada SGBD, por exemplo.

Código portável

Aqui, entenda como portabilidade a capacidade do Aurelius de atender a variados bancos de dados a partir de uma mesma base de código.
Num aspecto prático, o desenvolvedor estará lidando essencialmente com uma estrutura única de objetos, deixando a cargo do framework as
tratativas inerentes ao acesso e manipulação efetiva dos dados.

Bancos de dados suportados

O TMS Aurelius oferece suporte a uma enorme gama de banco de dados, o que acaba por englobar as principais opções do mercado atual.
Ao total são onze:

Firebird;
Interbase;
Oracle;
MS SQL Server;
MySQL;
PostgreSQL;
SQLite;
DB2;
ElevateDB Server;
Absolute Database;
Nexus DB.

Componentes de acesso a dados suportados

Conforme será visto mais adiante, o TMS Aurelius faz uso de componentes já tradicionais de acesso a dados para toda a parte de
conectividade de seu framework. Sendo assim, seu suporte oficial contempla as principais opções nativas e de terceiro, já bastante populares
em meio à comunidade, tais como:

dbExpress;
ADO (dbGo);
FireDac;
IBX (Interbase Express);
AnyDac;
IBObjects;
UniDac;
Zeos;
FIBPlus,
UIB (Unified Interbase).

Plataformas suportadas

Além disso, o TMS Aurelius também se caracteriza por ser uma solução multiplataforma, podendo ser usado tanto em projetos VCL quanto
em projetos FireMonkey (FMX), atendendo assim todas as plataformas suportadas pelo Delphi atualmente: Windows (32-bit e 64-bit),
MacOS, iOS e Android.

Conectividade a banco de dados


As nuances do TMS Aurelius são inúmeras, o que leva cada ponto do framework a contemplar uma enorme gama de conceitos e diretrizes
específicas. Obviamente este artigo primará pelos principais tópicos, tomando como base inicial o seu aspecto em termos de conectividade
com banco de dados.

IDBConnection e Adapters

Numa aplicação que faz uso do Aurelius, o objeto que representa uma conexão com o banco de dados é do tipo IDBConnection. Conforme
sua própria nomenclatura sugere, IDBConnection nada mais é do que uma Interface, declarada na unit Aurelius.Drivers.Interfaces do
framework. De forma prática, para qualquer objeto da aplicação que necessite se conectar ao banco para enviar ou receber dados, deverá
então necessariamente utilizar esta interface. Em seus bastidores, o que o IDBConnection faz é encapsular um componente de acesso a dados
existente, tomando pra si a conectividade pré-estabelecida, criando uma camada de uso transparente ao usuário. Assim, independente do
SGBD ou tecnologia de acesso a dados, a referência sempre será feita a um objeto IDBConnection.

Por fim, a obtenção de um IDBConnection se dá pelo uso de adaptadores (Adapters) que a implementam e que são providos pelo próprio
framework. A cada componente de acesso a dados suportado pelo Aurelius há um adaptador relacionado, declarado em uma unit específica.
Tomando como exemplo as duas bibliotecas nativas de acesso a dados mais populares, dbExpress e FireDac, temos:

dbExpress

Componente de acesso a dados: TSQLConnection


Classe adaptadora: TDBExpressConnectionAdapter
Unit: Aurelius.Drivers.dbExpress

FireDac

Componente de acesso a dados: TFDConnection


Classe adaptadora: TFireDacConnectionAdapter
Unit: Aurelius.Drivers.FireDac

A lista completa pode ser obtida na própria documentação do produto, disponível em seu site oficial (ver seção Links).

Conectando a uma base de dados

Uma vez de posse da base teórica do processo de conectividade do framework, sua transposição para o aspecto prático se torna bastante
facilitado. Na Listagem 2 é mostrado então um trecho de código de exemplo necessário para a obtenção de uma conexão válida.

Listagem 2. Conexão válida

var

MinhaConexao: IDBConnection;

begin

MinhaConexao := TFireDacConnectionAdapter.Create(FDConnection1, False);

end;

De acordo com o trecho mostrado, a obtenção de um objeto IDBConnection se dá pelo uso de um adaptador para FireDac
(TFireDacConnectionAdapter). Em termos gerais, o construtor dos diversos Adapters assume assinaturas padrões provindas de sobrecargas
de seu método Create. Para o exemplo mostrado, a assinatura possui a seguinte definição:

constructor Create(AConnection: T; AOwnsConnection: boolean);


AConnection refere-se ao componente de acesso a dados a ser utilizado, neste caso, um FDConnection (FDConnection1). Já
AOwnsConnection indica se o componente indicado será destruído ou não juntamente do IDBConnection. Uma vez definido como False, o
componente permanecerá em memória. Isso dá margens para, por exemplo, fazer uso do Aurelius em uma aplicação já existente, mantendo-
se em paralelo uma abordagem tradicional com DataSets e outra com uso de objetos apoiada pelo framework.

TMS Aurelius Connection

TMS Aurelius Connection faz referência a um Connection Wizard (assistente de conexão) provido pela instalação do framework, e que fica
disponível na seção de projetos do IDE (File > New > Other) conforme mostra a Figura 2. Sua função é apoiar o desenvolvedor na definição
da conexão a dados de uma aplicação Aurelius, solicitando para isso apenas duas informações (Figura 3): Driver e SQL Dialect.

Figura 2. TMS Aurelius DBConnection

Figura 3. Connection Wizard – definindo uma nova


conexão a dados

Driver faz referência ao componente de acesso a dados a ser utilizado enquanto que SQL Dialect diz respeito ao dialeto a ser considerado
durante a execução de instruções SQL. Isto porque, é sabido que cada SGBD possui suas próprias nuances de SQL, logo, esse tipo de
informação é vital para que o framework possa gerar as instruções SQL adequadas. Internamente no Aurelius cada SQL Dialect é
identificado por uma string, condizente ao banco relacionado, tal como „Firebird‟, „Interbase‟, „MySQL‟ e assim por diante. Para Adapters de
tecnologias multi-banco, como é o caso de FireDac e dbExpress, o framework é capaz de obter por si só o dialeto apropriado, por meio do
nome do driver associado ao componente de conexão. Por outro lado, há casos onde esta identificação não é possibilitada, tornando
obrigatório o provimento da indicação explícita do dialeto. Em vista disso, o assistente prevê o fornecimento do SQL Dilect desejado,
atendendo assim de forma segura ambos os cenários.

Após a finalização do Wizard é então criado um novo DataModule contendo o componente de acesso indicado, conforme mostra a Figura 4.
Adicionalmente, o assistente se encarrega também de gerar uma porção de código na unit do DataModule, que envolve, entre outras coisas, a
declaração do método que ficará responsável por retornar um IDBConnection válido, a ser utilizado por todo o projeto. A Listagem 3 exibe
o corpo deste método, tendo como base o cenário demonstrativo inicial com FireDac e Firebird.
Figura 4. Estrutura criada pelo Wizard

Listagem 3. Método CreateConnection criado pelo assistente

01 class function TFireDacFirebirdConnection.CreateConnection: IDBConnection;

02 var

03 DataModule: TFireDacFirebirdConnection;

04 begin

05 DataModule := TFireDacFirebirdConnection.Create(nil);

06 Result := TFireDacConnectionAdapter.Create(DataModule.Connection,

07 'Firebird', DataModule);

08 end;

CreateConnection é então o nome do método de classe criado pelo assistente e que se fundamenta pela instanciação do DataModule
(TFireDacFirebirdConnection) e retorno da interface IDBConnection. No Delphi, um class method nada mais é do que uma procedure ou
function que opera sobre uma referência de classe e não de objeto. Por fim, na linha 6 dessa listagem é possível ver a chamada ao construtor
da classe Adapter envolvida, neste caso TFireDacConnectionAdapter, que irá retornar a interface desejada. Conforme mencionado
anteriormente, no framework, este tipo de construtor apresenta várias sobrecargas. Assim, aqui é utilizada a seguinte assinatura, em que
ASQLDialect refere-se ao dialeto a ser utilizado (neste caso, „Firebird‟) e OwnedComponent indica o componente que será destruído
juntamente da interface (neste caso, o próprio Data Module):

constructor Create(AConnection: T; ASQLDialect: string; OwnedComponent: TComponent);

Mapeamento de classes

No contexto Orientado a Objeto ao qual o TMS Aurelius se encaixa, o início dos trabalhos efetivos com o framework realmente tem início
com a definição e mapeamento das classes de negócio da aplicação. Seguindo um aspecto mais purista, é neste ponto em que serão definidas,
por exemplo, as classes de negócio, e não mais uma visão linear pela simples criação das tabelas do banco de dados.

Com o uso do Aurelius, tais classes nada mais são do que classes Delphi simples, mapeadas ao propósito do framework. Este mapeamento é
a indicação dos detalhes da equivalência de cada ponto de sua estrutura, que servirá para que o Aurelius tome conhecimento sobre como esta
deverá ser persistida no banco de dados.

Automapping

Automapping é um recurso do Aurelius que, conforme seu próprio nome sugere, automatiza todo o processo de mapeamento de classes,
facilitando em muito a vida do desenvolvedor. Obviamente, assim como todo processo automatizado, seu uso requer certos cuidados, sendo
indicado para cenários mais iniciais. Para um melhor entendimento, nada melhor que uma demonstração baseada em código, assim, a
Listagem 4 exibe uma simples classe Delphi, denominada TRevista, já decorada com o atributo Automapping.

Listagem 4. Classe TAutor mapeada com Automapping


01 [Automapping]

02 [Entity]

03 TRevista = class

04 private

05 FId: Integer;

06 FAssunto: string;

07 FTituloCapa: string;

08 FEdicao: Integer;

09 public

10 property Id: Integer read FId write FId;

11 property Assunto: string read FAssunto write FAssunto;

12 property TituloCapa: string read FTituloCapa write FTituloCapa;

13 property Edicao: Integer read FEdicao write FEdicao;

14 end;

Em termos de código, esta classe é toda declarada utilizando código Delphi nativo, com exceção dos atributos Automapping, já citado e
Entity. Este último indica ao Aurelius que esta classe é uma entidade que poderá ser persistida no banco de dados. Além disso, para que os
atributos do framework possam ser reconhecidos e devidamente interpretados, é necessária a declaração da seguinte unit na seção uses da
codificação:

Aurelius.Mapping.Attributes

Assim, apenas por esta indicação de Automapping, o Aurelius consegue fazer as devidas associações para que esta classe seja espelhada em
uma tabela do banco de dados. Como exemplo, a entidade mostrada daria origem então a uma tabela nomeada como Revista, contendo
quatro colunas (ID, ASSUNTO, TITULO_CAPA e EDICAO) e uma chave primária (ID). Toda essa estrutura resultando pode ser visto na
Figura 5.

Figura 5. Estrutura da tabela REVISTA criada

Mapeamento manual

Conforme citado, o Automapping é indicado para cenários iniciais, uma vez que proporciona baixo controle de suas atribuições. Como
exemplo imediato é possível citar a estrutura criada anteriormente, onde as colunas da tabela Revista foram definidas de acordo com um
padrão estabelecido pelo próprio Aurelius. Assim, os campos string foram mapeados para campos VARCHAR de tamanho máximo (255) e
obrigatório (Not Null).

Isso significa dizer que num cenário real, em que há a necessidade por um controle total da estrutura de entidades que está sendo definida, é
inevitável o uso de um mapeamento manual. Para estes casos, o framework conta com um leque bastante vasto de atributos que irão
“decorar” uma classe, cada qual com um objeto específico. Para melhor entendimento dos conceitos envolvidos, nada melhor do que traçar
um paralelo prático de uso. Sendo assim, a Listagem 5 exibe a definição do mesmo modelo de classe utilizado anteriormente, agora com um
mapeamento customizado, seguindo as diretrizes do Aurelius. Na sequência são expostos os conceitos envolvidos a cada atributo utilizado.
Vale ressaltar que esta estrutura de classe servirá então como base por todo o artigo.

Listagem 5. Mapeamento da classe TRevista

01 [Entity]

02 [Id('FId', TIdGenerator.IdentityOrSequence)]

03 [Sequence('SEQ_REVISTA')]

04 [Table('REVISTA')]

05 TRevista = class

06 private

07 [Column('REVISTA_ID', [TColumnProp.Required])]

08 FId: Integer;

09 [Column('ASSUNTO', [TColumnProp.Required], 80)]

10 FAssunto: string;

11 [Column('TITULO_CAPA', [], 80)]

12 FTituloCapa: Nullable<string>;

13 [Column('EDICAO', [TColumnProp.Required])]

14 FEdicao: Integer;

15 public

16 property Id: Integer read FId write FId;

17 property Assunto: string read FAssunto write FAssunto;

18 property TituloCapa: Nullable<string> read FTituloCapa

19 write FTituloCapa;

20 property Edicao: Integer read FEdicao write FEdicao;

21 end;

Sem deixar de citar, a Figura 6 ilustra o resultado obtido pelo mapeamento manual da classe REVISTA.

Figura 6. Nova estrutura da tabela REVISTA

Entity
Entity é um atributo básico, já citado anteriormente, que diz ao framework que a classe especificada é uma Entidade. No Aurelius, toda
entidade pode ser persistida no banco de dados.

Id

“Id” Indica o elemento da classe que será usado como seu identificador único, tal qual o conceito de Chave Primária em uma tabela do banco
de dados. No contexto de uma Classe, este elemento poderá ser então um campo (Field) ou propriedade (Property). Esse tipo de atribuição é
uma determinação do próprio framework que exige que todo objeto possua uma identificação única, para que possa ser devidamente
manipulado.

Adicionalmente, como parâmetro, é passado um valor prefixado com TIdGenerator, que indica a forma com que o valor será gerado para o
identificador. IdentityOrSequence determina então que tal valor será gerado pelo banco de dados, seja por meio de campo auto incremento,
ou objetos Generator e Sequence, bastante tradicionais em bases InterBase e Firebird. Nesta situação, o framework apenas espera pelo valor
gerado. Além de IdentityOrSequence, outras opções são disponibilizadas, tal qual None, que deixa a cargo da aplicação a geração do valor do
identificador.

Sequence

Para os casos em que há o envolvimento de um objeto Sequence ou Generator de banco de dados, tal como é o do exemplo proposto, o
atributo “Sequence” deverá ser utilizado para indicá-los de forma explícita ao framework. Sendo assim, na maioria dos casos, seu uso fica
atrelado à própria definição do Id.

Table

O atributo Table pode ser tido como essencial, uma vez que faz o mapeamento da classe a uma tabela do banco de dados. Entre outras coisas,
isso determina que todo objeto oriundo desta classe será salvo como um registro desta tabela. Assim, tomando como base a classe Revista de
exemplo, seu mapeamento se dá a uma tabela Revista no banco de dados. Na aplicação, conforme será visto adiante, uma vez que uma
instância de Revista é salva no contexto do Aurelius, seus dados são persistidos em forma de registro na tabela citada.

Column

A função do atributo Column é bastante intuitiva uma vez que mapeia o campo da Classe à coluna na tabela do banco de dados. Como
argumentos, recebe a indicação de campo requerido (TColumnProp.Required) e seu tamanho, para os casos de campos String.

Nullable types

Em frameworks ORM o termo “Nullable types” refere-se a tipos que podem receber a atribuição de nulidade (null). Visto isto no lado
prático, é o que ocorre com grande parte dos campos que não possuem preenchimento obrigatório, ou seja, que podem ter um valor associado
ou permanecem sem valor algum (null). Numa situação natural com o TMS Aurelius, pressupõe-se então que a simples definição de um
campo na classe e seu mapeamento a uma coluna de uma tabela no banco de dados já seria suficiente para se trabalhar com valores nulos. No
entanto, os tipos primitivos do Delphi não suportam valores nulos, o que acarreta erros em runtime (Figura 7). Em vista disso, o Aurelius
apresenta o tipo Nullable<T>, declarado na unit Aurelius.Types.Nullable, que já faz o tratamento automático para os casos de nulidade ao
campo.

Figura 7. Erro ao tentar atribuir um valor nulo a um tipo


primitivo do Delphi
Tomando como base a codificação de exemplo, o campo FCapa é então declarado como sendo do tipo Nullable<string>. Logo, caso este não
receba valor algum, ele será devidamente persistido como null no banco.

Blobs

Indo além do exemplo, outro tipo bastante comum ao desenvolvimento de aplicações de banco de dados é o tipo Blob. Tradicionalmente ele
é utilizado para armazenar dados binários (ex: imagens, documentos, planilhas) em uma dada coluna da tabela do banco. No Aurelius,
campos Blob não recebem uma marcação especial, mas sim uma tipagem de dados especificada. Por recomendação, deve-se utilizar o tipo
TArray<byte> ou TBlob, que fazem referência a uma matriz de bytes. Este último é o mais recomendado por questões de usabilidade do
próprio framework.

Essa usabilidade citada se refere a outro recurso atrelado ao uso deste tipo, denominado Lazy-Loading Blobs. Numa tradução livre para o
português, o termo lazy-loading equivale a um “carregamento preguiçoso”, o que resume bem sua funcionalidade. Como se sabe, uma
informação binária pode possuir uma grande quantidade de bytes associados, tal como uma imagem grande. Logo, o carregamento de
campos Blobs pode acabar por degradar bastante a aplicação, em termos de performance, comprometendo assim a usabilidade do usuário.

Pensando nisso, o TMS Aurelius estabelece que os campos Blobs marcados como Lazy-Loading não sejam recuperados num primeiro
momento, e sim somente quando forem requisitados. Na prática, sua definição ocorre da seguinte forma:

[Column(‘IMAGEM_CAPA’, [TColumnProp.Lazy]) FImagemDeCapa: TBlob;

O legado: chave primária composta

Conforme pôde ser observado, o conceito trazido pelo TMS Aurelius incentiva o uso de identificadores únicos a cada objeto, o que mantém
sua devida singularidade, bem como simplifica todo o aspecto de negócio e conhecimento envolvido. Na prática, a tendência então se
estabelece pela criação de classes contendo um elemento Id, que é o seu identificador tanto no contexto da aplicação, quanto no contexto do
banco de dados (chave primária da tabela). Todavia, o propósito do Aurelius não é somente atender a construção de novas aplicações, que já
partem de tal princípio, mas também o seu uso em projetos legados, que já possuem toda uma estrutura constituída.

Para estes casos, onde ainda não há a definição de um modelo de classes de negócio na aplicação, mas sim apenas um banco de dados
definido, as tabelas se tornam peças fundamentais para o início de uma eventual “migração” para o uso do framework. Neste cenário, um
ponto bastante comum é a presença de tabelas com identificadores compostos (chave primária composta), em que um registro não é
identificado por um único valor (ex: Id), mas sim por vários.

Tomando como base o banco Employee que acompanha o Firebird, nele existe uma tabela denominada Job, que estabelece uma Primary Key
com três campos: JOB_CODE, JOB_GRADE e JOB_COUNTRY, conforme mostra a imagem a seguir (Figura 8). O detalhe aqui fica por
conta do campo JOB_COUNTRY, que refere-se a uma chave estrangeira (Foreign Key) associada à tabela COUNTRY.

Figura 8. Estrutura da tabela Job do banco Employee

Em um caso como este, tendo em mente a adoção do Aurelius, a simples transformação de uma chave tripla para uma chave única poderia
acarretar uma série de problemas indesejáveis a uma aplicação já previamente estruturada e em funcionamento. Pensando nisso, o framework
dispõe de recurso para o mapeamento de classe com identificador composto. Na verdade, não se trata de uma marcação exclusiva ou
especial, mas sim a possibilidade de se mapear vários campos como Id, tal como mostrado na Listagem 6

Listagem 6. Mapeamento de exemplo usando identificador composto


01 [Entity]

02 [Table('COUNTRY')]

03 [Id('FCountry', TIdGenerator.None)]

04 TCountry = class

05 private

06 [Column('COUNTRY', [TColumnProp.Required], 15)]

07 FCountry: string;

08 [Column('CURRENCY', [TColumnProp.Required], 11)]

09 FCurrency: string;

10 public

11 property Country: string read FCountry write FCountry;

12 property Currency: string read FCurrency write FCurrency;

13 end;

14

15 [Entity]

16 [Table('JOB')]

17 [Id('FJobCode', TIdGenerator.None)]

18 [Id('FJobGrade', TIdGenerator.None)]

19 TJob = class

20 private

21 [Column('JOB_CODE', [TColumnProp.Required], 5)]

22 FJobCode: string;

23 [Column('JOB_GRADE', [TColumnProp.Required])]

24 FJobGrade: Integer;

25 [JoinColumn('JOB_COUNTRY', [TColumnProp.Required])]

26 FJobCountry: TCountry;

27 public

28 property JobCode: string read FJobCode write FJobCode;

29 property JobGrade: Integer read FJobGrade write FJobGrade;

30 property JobCountry: TCountry read FJobCountry write FJobCountry;


31 end;

Nesta codificação de exemplo, primeiramente é apresentado o mapeamento da classe TCountry, que ocorre de forma bastante tradicional.
Sua exposição aqui serve apenas para facilitar o entendimento do conceito apresentado. A seguir, a classe TJob é mapeada para uma tabela
JOB, tendo dois de seus campos (FJobCode e FJobGrade) marcados como Id. De forma complementar, é feito também o mapeamento de
cada um deles para o campo desejado na tabela: JOB_CODE e JOB_GRADE, respectivamente.

Já o terceiro campo identificador, FJobCountry, por se tratar de uma FK, reflete-se no contexto da classe como sendo um elemento do tipo
TCountry. Neste ponto, um novo conceito se estabelece, relacionado ao atributo utilizado para mapeá-lo.

JoinColumn

Em ocasiões como esta o atributo JoinColumn é então utilizado para indicar o campo que será usado na associação, representando a coluna
“chave estrangeira” da tabela. Já no seu lado prático, no exemplo mostrado ele foi declarado com dois argumentos, ambos referentes à coluna
relacionada na tabela do banco. O primeiro nada mais é do que o nome da coluna, conforme já visto em outras exemplificações. Já o
segundo, TColumProp, é um tipo que especifica determinada propriedade à coluna, conforme mostrado a seguir:

TColumnProp.Required: indica que a coluna é de preenchimento obrigatório, o tradicional NOT NULL de banco de dados;
TColumnProp.Unique: indica que os valores para esta coluna devem ser únicos, para tal, um índice Unique Key será criado na tabela do banco
de dados;
TColumnProp.NoInsert: indica que o valor deste campo não será persistido no banco de dados em situações de inserção (insert);
TColumnProp.NoUpdate: indica que o valor deste campo não será persistido no banco de dados em situações de atualização (update).

Manipulação da estrutura de banco de dados

TDatabaseManager é a classe presente no framework responsável por manipular a estrutura de um banco de dados. Aqui entenda manipular
como sendo o processo de criação ou atualização estrutural baseado no seu modelo de classes. Para isso são utilizados basicamente dois
métodos: BuildDatabase e UpdateDatabase. Uma vez chamado, o primeiro irá executar as instruções SQL necessárias para a criação de uma
estrutura de banco de dados que seja condizente com a estrutura de classes do projeto mapeadas como entidade. Em termos de código, o
trecho exibido na Listagem 7 exemplifica seu uso. O detalhe aqui fica por conta de seu construtor, que recebe como parâmetro um objeto do
tipo IDBConnection que, conforme já visto, representa a conexão com o banco de dados no contexto do Aurelius. Sem deixar de mencionar,
seu uso requer a declaração da unit Aurelius.Engine.DatabaseManager.

Listagem 7. Database Manager – Método BuildDatabase

01 var

02 MyDBManager: TDatabaseManager;

03 begin

04 MyDBManager := TDatabaseManager.Create(MyConnection);

05 MyDBManager.BuildDatabase;

06 MyDBManager.Free;

07 end;

Já o método UpdateDatabase atua de uma forma um pouco mais complexa, uma vez que sua própria ação exige uma maior profundidade de
detalhes. O ponto aqui é que o framework tem que manter a estrutura existente íntegra, ao mesmo tempo em que aplica as novas mudanças
necessárias. Para isso ele trabalha em duas etapas. Na primeira ocorre um processo de validação de estruturas (schemas), onde se compara o
schema atual da aplicação com o schema do banco de dados já existente. A diferença, caso haja, dá origem então ao script SQL necessário
para a atualização. Seguindo por este caminho, a segunda etapa se resume na efetiva execução deste script no banco, tornando-o condizente
com a estrutura presente na aplicação. Em complemento aos métodos BuildDatabase e UpdateDatabase está o método DestroyDatabase que
se refere à exclusão da estrutura do banco de dados existente.
Manipulação de objetos

A manipulação de objetos diz respeito às operações CRUD (Create-Read-Update-Delete) que, num contexto de aplicações de banco de
dados, podem ser traduzidas basicamente nas instruções de Insert, Select, Update e Delete, respectivamente.

Object Manager

Object Manager é o elemento provido pelo TMS Aurelius para a manipulação dos objetos da aplicação. Ele é implementado por meio da
classe TObjectManager, presente em Aurelius.Engine.ObjectManager, que é então a unit que deverá ser declarada no código do projeto para
o seu uso. Em termos conceituais, ele atua como uma camada entre a aplicação e o banco de dados, provendo os métodos necessários para as
operações CRUD.

Devido à própria robustez do framework, vários são os métodos disponibilizados neste cenário, usuais a vários contextos diferentes. Em vista
disso, a seguir são explicitados alguns dos principais.

Save

O método Save pode ser considerado o mais básico dentre os disponíveis, uma vez que sua função é a de inserir dados dentro do banco de
dados. Para tal, ele então manipula os objetos de entidades da aplicação, persistindo suas informações em uma ou mais tabelas. Tendo como
base o exemplo mostrado até aqui, uma chamada a Save para se persistir um objeto Revista se daria da mesma forma que a apresentada na
Listagem 8.

Listagem 8. Salvando um objeto no banco de dados

01 var

02 MyRevista: TRevista;

03 MyObjectManager: TObjectManager;

04 begin

05 // Objeto Revista

06 MyRevista := TRevista.Create;

07 MyRevista.Assunto := 'Delphi';

08 MyRevista.TituloCapa := 'Fique por dentro do TMS Aurelius';

09 MyRevista.Edicao := 100;

10

11 // Object Manager

12 MyObjectManager := TObjectManager.Create(

13 TFireDacFirebirdConnection.CreateConnection);

14 MyObjectManager.Save(MyRevista);

15 MyObjectManager.Free;

16 end;
Olhando pelo lado prático, neste momento é importante ressaltar que a instanciação de um objeto do tipo TObjectManager se dá de forma
semelhante a um TDatabaseManager, já visto anteriormente. Assim, ao seu construtor (Create) deve-se passar um parâmetro de conexão
(IDBConnection), conforme a seguir:

MyObjectManager := TObjectManager.Create(MyConnection);

Voltando ao código apresentado, das linhas 5 a 9 é criado manualmente um objeto denominado MyRevista do tipo TRevista. A seguir, na
linha 12 o ObjectManager entra em ação, recebendo como parâmetro em seu construtor a conexão definida no início deste artigo por meio do
Wizard. Por fim, na linha 14 o método Save é chamado, ocorrendo então a persistência do objeto no banco de dados, conforme pode ser visto
na Figura 9.

Figura 9. Registro salvo no banco de dados Firebird

Update

O método Update também é utilizado para persistir informações, atualizando os dados de um objeto existente no banco de dados.
Funcionalmente, ele se encarrega então de atualizar o devido registro na tabela do banco, baseado no valor da propriedade chave primária do
objeto envolvido. Sua chamada segue o mesmo padrão do Save:

MyObjectManager.Update(MyRevista);

SaveOrUpdate

SaveOrUpdate é o método que, conforme seu próprio nome sugere, faz um mix entre os métodos Save e Update. Sendo assim, se o objeto
passado contém um identificador válido, indicando uma atualização, um Update é realizado, caso contrário, o Save é acionado. Um exemplo
de sua chamada é mostrado a seguir:

MyObjectManager.SaveOrUpdate(MyRevista);

Remove

O método Remove é um complemento aos métodos Save e Update, uma vez que fica responsável por remover o objeto do contexto da
aplicação, bem como excluir o registro relacionado no banco de dados, tendo como base o seu identificador:

MyObjectManager.Remove(MyObject);

FindAll

FindAll é o método provido para recuperar todas as instâncias de objeto de uma determinada classe. Num comparativo com o aspecto
tradicional de banco de dados, ele funciona como uma espécie de “Select * from Tabela”. Seguindo pelo contexto do framework, uma
chamada a FindAll faz com que uma lista de objetos seja retornada (TObjectList):

MyObjects := MyObjectManager.FindAll<TRevista>;

Find<T>
Ao contrário de FindAll, o método Find é designado para a busca de objetos, tomando como base o uso de critérios (criteria no jargão de
frameworks ORM). Novamente fazendo uma comparação com o cenário tradicional de aplicações de banco de dados, o método Find seria
algo como um Select com Where. A seguir temos uma, dentre as várias possíveis formas de sua chamada:

MyObject := MyObjectManager.Find<TRevista>(Id);

O método Flush

Ainda sobre a manipulação de objetos, o framework conta ainda com outros métodos que podem ser tidos como “complementares” num
estudo inicial, mas que se tornam essenciais à medida que sua experiência com o framework aumenta. Em síntese, uma vez que é chamado o
método Flush, ele irá persistir todos os objetos pendentes de atualização que estão sob a alcunha do Aurelius naquele momento.

Nesta situação, diferente do método Save, onde um único objeto é passado como argumento para ser persistido, aqui uma única chamada a
Flush fará com que um ou vários objetos sejam salvos no banco. Perceba que esse tipo de recurso contribui no atendimento dos mais
variados cenários apresentados por uma aplicação de banco de dados. De forma ilustrativa, o trecho de código da Listagem 9 apresenta seu
uso. Na situação apresentada, com o uso do Flush, tanto o objeto MyRevista1 quanto o objeto MyRevista2 terão sua coluna “Edição”
atualizada no banco de dados.

Listagem 9. Exemplo de uso do método Flush

01 // Object Manager

02 MyObjectManager :=

03 TObjectManager.Create(

04 TFireDacFirebirdConnection.CreateConnection);

05

06 // Objeto Revista 1

07 MyRevista1 := MyObjectManager.Find<TRevista>(1);

08 MyRevista1.Edicao := 10;

09

10 // Objeto Revista 2

11 MyRevista2 := MyObjectManager.Find<TRevista>(2);

12 MyRevista2.Edicao := 11;

13

14 // Flush

15 MyObjectManager.Flush;

16 MyObjectManager.Free;

Neste código, a linha 2 é crucial para o seu funcionamento, onde temos a instanciação de um objeto TObjectManager, o que é utilizado nas
linhas 7 e 11, por meio de seu método Find, para recuperar os objetos desejados. Finalmente na linha 15 é feita uma chamada a Flush, que
fará a atualização dos dois objetos no banco de dados.
Um alerta a se fazer com o uso do Flush é com relação à sua performance, que pode ser degradada caso o número de objetos gerenciados
pelo Manager seja muito grande. Isto porque, internamente o método faz uma varredura por todas as entidades atreladas ao ObjectManager,
verificando quais delas necessitam ser persistidas. Logo, quanto mais objetos, mais tempo é demandado ao processo.

Sem deixar de citar, o método Flush possui ainda uma sobrecarga cuja finalidade se aproxima do método Save, uma vez que é direcionado à
persistência de um único objeto, conforme mostrado a seguir:

MyObjectManager.Flush(MyRevista);

TAureliusDataset

Único componente trazido pelo TMS Aurelius em sua instalação, o TAureliusDataset nada mais é do que um componente de ligação de
dados (Data Binding) descendente do tradicional TDataSet. Isso o torna compatível com os diversos controles data-aware do Delphi,
presentes tanto na VCL quanto na FMX (FireMonkey). Assim, por meio de seu uso é que podemos ligar os objetos oriundos das entidades do
Aurelius a esses controles visuais que irão compor as telas da aplicação.

SetSourceList e SetSourceObject

Assim como qualquer outro componente descendente de TDataSet, o TAureliusDataSet também necessita de uma fonte de dados (data
source) associada, tornando então estes dados disponíveis para manipulação e visualização. Para isso, ele disponibiliza dois métodos,
nomeados sugestivamente como SetSourceList e SetSourceObject, respectivamente. O primeiro é usado para os casos onde se quer associar
uma Lista de objetos ao DataSet, enquanto que o segundo associa apenas um único objeto independente. Apenas como ilustração, a seguir
são mostrados exemplos de chamadas a SetSourceList e SetSourceObject:

MyAureliusDataSet.SetSourceList(MyRevistas); MyAureliusDataSet.SetSourceObject(MyRevista);

Indo além com TMS Aurelius

Atendidas as expectativas com as aplicações de banco de dados para VCL e FireMonkey, o Aurelius vai além e proporciona usabilidade
também em cenários distribuídos. Para a comunidade, um bom exemplo de distributed applications são as aplicações DataSnap (BOX 1), que
se caracterizam por prover um meio comum (Servidor de Aplicação) de acesso a recursos e serviços a clientes de qualquer tipo, seja ele
escrito nativamente em Delphi, Java, PHP, JavaScript, etc.

Logo, para que os dados trafeguem entre os lados (Server e Client), torna-se necessária a utilização de uma notação comum, tal como o
formato JSON. Para estes casos, o framework conta com classes de conversão tanto de objeto para JSON quanto de JSON para objeto.
Didaticamente elas são tratadas como classes Serializer e Deserializer. Como forma de exemplificação, novamente tendo como base um
objeto do tipo Revista, sua serialização para JSON se daria da mesma forma que a apresentada na Listagem 10.

Listagem 10. Serialização para JSON

var MySerializer: TDataSnapJsonSerializer;

begin

...

MySerializer:= TDataSnapJsonSerializer.Create;

MyJsonValue := MySerializer.ToJson(MyRevista);

...

end;

BOX 1. DataSnap
Anteriormente conhecida como MIDAS, DataSnap é uma tecnologia da Embarcadero que permite a criação de aplicativos multicamadas de forma
totalmente RAD. Para tal a IDE traz uma gama de componentes prontos, cada atendendo cada aspecto da arquitetura, incluindo conectividade TCP/IP,
HTTP e HTTPS, suporte a REST, autenticação, encriptação e compressão de dados, entre outros recursos.

Historicamente, no contexto do Delphi, o uso da POO na construção de uma aplicação de banco de dados é algo bastante raro. Basicamente
dois são os principais fatores que levam a isso. O primeiro está diretamente relacionado ao nível de excelência RAD provido pela ferramenta,
através do uso de bibliotecas como dbExpress, FireDac, entre outras. Já o segundo fator, e talvez o mais importante, se dá pela ausência de
um framework ORM nativo, que incentive o desenvolvedor a adotá-lo de forma prática e facilitada. Diante disso, o TMS Aurelius vem para
preencher justamente esta lacuna, através de sua eficiência e, principalmente, sua facilidade de uso.

Neste artigo você pôde conferir como é fácil entender a mecânica de uso do framework e, conforme a prática, essa facilidade se transpõe para
o dia-a-dia da codificação. Código mais enxuto, portabilidade e foco na regra de negócio são alguns dos fatores positivos que sua adoção irá
proporcionar.

Talvez o ponto chave aqui não seja a escolha pela continuidade de uma prática já tradicional ou a adoção de uma totalmente nova, mas sim a
abertura de sua mente. Em suma, a dica que fica é: pense fora da caixa, se exponha a novos conceitos, aplique-os e se desenvolva
internamente. Com a programação não há limites, apenas você. Espero que tenham gostado do artigo e nos vemos na próxima. Bons
desenvolvimentos!

Links

Página oficial do Delphi


Página de download da versão de avaliação (Trial) do Delphi Berlin 10.1
Página de download da versão gratuita da ferramenta Delphi 10.1 Berlin Starter Edition
Página do TMS Aurelius

Receba nossas novidades


por Fabricio Hissao (0) (0)

Ficou com alguma dúvida?

Potrebbero piacerti anche