Introdução
À medida que vamos aprimorando o nosso desenvolvimento, utilizando os princípios de desenvolvimento orientado a objetos e técnicas de padrões de projetos, é praticamente obrigatório o aprendizado de conceitos mais avançados na linguagem em que se está desenvolvendo.
Este post tomará como base para a explanação uma situação real e muito comum que os desenvolvedores enfrentam no dia a dia. Temos que desenvolver um método no servidor capaz de cadastrar os itens de um carrinho de compras. Situação bastante simples, mas que requer certo conhecimento, tanto na linguagem, como em conceitos de orientação a objetos.
Quando se pensa neste problema em que temos que criar uma solução, sempre procurando utilizar as melhores práticas de desenvolvimento, encontramos basicamente duas possíveis soluções:
1º) Criar um método no servidor que receberá como parâmetro uma classe, que no caso é o produto, e quando formos adicionar os produtos do carrinho temos que fazer um laço enviando um a um os produtos para o servidor persistir.
2º) Criar um método no servidor que receberá como parâmetro uma lista de produtos a qual será adicionada pela aplicação cliente para que o servidor se encarregue de persistir esses produtos no banco de dados.
Foram apresentadas duas possíveis soluções para o nosso problema, mas vem a grande pergunta: Qual desses dois métodos ou arquitetura será a melhor solução para o problema em questão?
Vamos analisar as possíveis soluções para identificar a melhor para a resolução do nosso problema.
A primeira é uma boa solução devido a sua simplicidade, mas se analisar mais profundamente perceberá que a sua simplicidade é simplesmente superficial. Na verdade muita das soluções rápidas que tomamos são apenas ilusão, pois muitas das vezes só olhamos em curto prazo, mas um sistema, principalmente corporativo, deverá ter um ciclo de vida grande e essa solução rápida poderá custar muito na manutenção futura do código. Se tivermos que acrescentar um controle de transação nesta persistência, perceberemos então que a solução simples se tornou um problema maior do que tínhamos anteriormente.
Já a segunda solução, ela requer um conhecimento maior do desenvolvedor, mas em compensação teremos mais facilidade na manutenção do nosso código. Um grande exemplo dessa facilidade é o mesmo usado anteriormente, pois se realmente houver a necessidade de um controle de transação, ficará bem mais fácil tal implementação.
Já que no nosso estudo de caso a solução melhor foi a segunda, então vamos focar nela pra explanar o objetivo desse post.
Para armazenar os produtos será utilizada a classe TList<T> da unit System.Generics.Collections para quem possui o Delphi XE2 e XE3 e para as versões anteriores será utilizada a da unit Generics.Collections.
Primeiramente vamos entender o conceito de Generics, para só então trabalharmos na transferência dos produtos para o servidor. Algo muito importante que deve ser salientado neste artigo é que ele foi escrito para que desenvolvedores de níveis diferentes possam tirar proveito deste conteúdo, e é por isso que o mesmo sempre procura exemplificar e explicar cada assunto abordado no mesmo.
Segundo o Delphi Reference (material utilizado como guia de estudo para se tirar a certificação Delphi Developer), Generics são conjuntos de ferramentas de abstração que permitem a dissociação de um algoritmo (tal como procedure e function) ou de uma estrutura de dados (Como uma Class, Record, Interface) a partir de um ou mais tipos particulares que a estrutura de dados ou algoritmo utiliza. Caso tenha a curiosidade de aprender e entender mais obre o conceito e a utilização dos Generics, é recomendado utilizar o Delphi Reference, pois possui muitos exemplos mencionados lá. Está aí o link para o Delphi Reference falando sobre tipos Generics ou types Generics, como achar melhor: http://docwiki.embarcadero.com/RADStudio/XE3/en/Generics_Index.
Observação: O foco desse artigo não é falar sobre Generics e, por isso, não vamos abordar tão profundamente esse assunto.
Depois desta abordagem conceitual, estamos aptos para iniciamos as práticas.
Prática
Vamos iniciar um novo projeto DataSnap utilizando o Delphi XE2, mas quem tiver as versões do Delphi 2010 em diante provavelmente não terá qualquer problema para acompanhar este artigo.
Vamos em: File - New – Other.
Figura 1: Iniciando um novo Projeto
Vamos criar um novo projeto com o wizard do DataSnap que está na pasta DataSnap Server, clicar na opção DataSnap Server e depois em Ok. Será aberto o wizard onde vamos passar as informações básicas de funcionamento do servidor.
Figura 2: Selecionando o projeto DataSnap
Observação: Não entraremos em detalhes sobre cada opção que tem no wizard, pois esse não é o foco desse artigo.
Na primeira parte do wizard vamos escolher VCL Forms Applications. Como mostra a figura 3.
Figura 3: Etapa 1 de 4 na configuração do Servidor
Clique em Next.
Na próxima etapa deixe o padrão e na etapa 3/4, selecione uma porta qualquer. Neste artigo foi escolhida a porta 8565.
Figura 4: Etapa 3 de 4 na configuração do Servidor
Clique em Next. Na etapa 4/4 deixe como está e clique em Finish.
Pronto. Já foi criado o nosso projeto. Foi criando pelo wizard um form, um Server Method de exemplo e um Server Container.
Para facilitar o desenvolvimento foram renomeadas as units da seguinte forma:
Antigo nome | Novo Nome |
Form1 | UFViewPrincipal |
ServerMethodsUnit | USMMetodos |
ServerContainerUnit | USCServidor |
Tabela 1: Nomes das unidades
Vamos adicionar uma nova unit e nela criaremos uma classe chamada TProduto com as seguintes propriedades e os seguintes tipos, como mostra a Tabela 2, e salvaremos com o nome UProduto.
Propriedade | Tipo |
Nome | String |
Valor | Double |
Tabela 2: Informações da classe TProduto
A casse TProduto, no nosso exemplo, só vai possuir dois atributos, que no caso é Nome e Valor. Claro que no projeto real essa classe TProduto vai conter vários atributos, podendo ter até métodos, mas esse não é o foco desse artigo.
Agora vamos criar um tipo ou um alias para a nossa lista e para isso vamos ciar embaixo da nossa classe TProduto o type TItensProduto, como mostra a Listagem 1.
Listagem 1: Implementação da unit TProduto
unit UProduto;
interface
uses System.Generics.Collections;
type
TProduto = class
private
prv_sNome : string;
prv_fValor : Double;
public
property Nome: string read prv_sNome write prv_sNome;
property Valor: Double read prv_fValor write prv_fValor;
end;
TItensProduto = TObjectList<TProduto>;
implementation
Agora vamos adicionar um novo Server Module em File -> New -> Other, como mostra a Figura 5.
Figura 5: Etapa para adicionar um novo server module
Selecione a pasta DataSnap Server e depois clique duas vezes em Server Module, como mostra a Figura 6.
Figura 6: Adicionando no projeto um novo Server Module
Vamos alterar a propriedade Name do Server Module para SMProduto e salvar a Unit com o nome USMProduto.
Feito isso, vamos por a mão na massa para o que realmente interessa, que é o envio de classes Generics. Vamos primeiramente programar a deserialização no servidor no Server Module SMProduto.
Vamos adicionar as units Data.DBXJSON , Data.DBXJSONReflect, System.Generics.Collections para quem tem o Delphi a partir da versão XE2 e para as demais versões adicione as units DBXJSON, DBXJSONReflect, Generics.Collections e também sem esquecer de adicionar a unit UProduto.
Vamos adicionar um método chamado pInserirItensVenda com a seguinte definição, como mostra a Listagem 2.
Listagem 2: Declaração do método pInserirItensVenda
procedure pInserirItensVenda (pItensVenda : TJSONValue);
O Parâmetro pItensVenda é o objeto serializado, então vamos fazer o processo de deserialização como mostra a Listagem 3.
Listagem 3: Implementação do método pInserirItensVenda
procedure TSMProduto.pInserirItensVenda (pItensVenda : TJSONValue);
var UnMarshal : TJSONUnMarshal;
oItensVenda : TItensVenda;
CTX : TRttiContext;
begin
CTX.GetType(TypeInfo(TItensVenda));
if pItensVenda is TJSONNull then //Verificando se o objeto vindo do servidor é igual a null ou nil.
Exit;
//Instanciando o TJSONUnMarshal, responsável por deserializar o objeto
UnMarshal := TJSONUnMarshal.Create;
try
//Deserializa o objeto JSON e faz um TypeCast para trasnformar efetivamente em TCliente
oItensVenda := TItensVenda(UnMarshal.Unmarshal(pItensVenda));
finally
UnMarshal.Free; //Libera o Objeto da memoria
end;
end;
Vamos agora adicionar uma aplicação cliente selecionando o Grupo e depois com o botão direito selecionando Add New Project (Figura 7). Selecione a pasta Delphi Project e depois clique duas vezes em VCL Forms Application como mostra a Figura 8.
Figura 7: Adicionando um projeto cliente
Figura 8: Adicionando uma aplicação Cliente
Com o projeto já criado vamos renomear a unit do Form para UFViewPrincipal e o nome do form para FViewPrincipal.
Vamos Adicionar um TMemo, um TButton e um TSQLConnection ao form e vamos renomeá-los como mostra a Tabela 3.
Nome da classe | Novo Nome |
TMemo | MJSON |
TButton | BInserirItensVenda |
TSQLConnection | SQLCServidor |
Tabela 3: Nomes dos compontens do form
Organize sua tela para que fique parecido com a Figura 9.
Figura 9: Organização da tela
Vamos fazer as devidas configurações para que o SQLCServidor possa se conectar com a aplicação servidora.
Observação: Não veremos detalhes sobre cada propriedade do TSQLConnection, pois esse não é o foco do artigo.
Vamos configurar o TSQLConnection segundo a Tabela 4.
Propriedade | Novo Valor |
Drive | Datasnap |
Port | 8565 |
Tabela 4: Configurando as propriedades do TSQLConnection
Clique com o botão direito no SQLCServidor e em Generate DataSnap cliente classes, como mostra a Figura 10.
Figura 10: Gerando Classe Proxy
Pronto. Será gerada uma classe Proxy responsável por se conectar com o servidor. Renomeie a unit para UProxy. Vamos agora implementar o botão BInserirItensVenda, como mostra a Listagem 4.
Listagem 4: Implementação do botão BInserirItensVenda
procedure TFViewPrincipal.BInserirItensVendaClick(Sender: TObject);
var oItensVenda : TItensVenda;
oProduto : TProduto;
oProxy : TSMProdutoClient;
Marshal : TJSONMarshal;
oObjetoJSON : TJSONValue;
begin
oItensVenda := TItensVenda.Create(True); //Informa para a lista que os objetos que estiverem na lista também serão liberados juntos com a lista
try
{$REGION 'Passando os produtos para a lista de objetos'}
oProduto := TProduto.Create;
oProduto.Nome := 'Produto 01';
oProduto.Valor := 25;
oItensVenda.Add(oProduto);
oProduto := TProduto.Create;
oProduto.Nome := 'Produto 02';
oProduto.Valor := 27;
oItensVenda.Add(oProduto);
oProduto := TProduto.Create;
oProduto.Nome := 'Produto 03';
oProduto.Valor := 2527;
oItensVenda.Add(oProduto);
{$ENDREGION}
if Assigned(oItensVenda) then //Verificando se o objeto foi criado
begin
Marshal := TJSONMarshal.Create; //Instanciando o objeto responsável por serializar
try
oObjetoJSON := Marshal.Marshal(oItensVenda);//Serializando de fato o objeto oItensVenda para JSON
finally
Marshal.Free; //Liberando o serializador
end;
end
else
//Caso o Objeto oItensVenda não tenha sido instânciado será enviado para o servidor um objeto do tipo JSONNull
oObjetoJSON := TJSONNull.Create;
SQLCServidor.Connected := True; //Se conectando com o servidor
//Criando o proxy do Server Module TSMCliente
oProxy := TSMProdutoClient.Create(SQLCServidor.DBXConnection);
MJSON.Text := oObjetoJSON.ToString; //Joga o objeto JSON no memo
oProxy.pInserirItensVenda(oObjetoJSON); //Envia os itens da venda pro servidor
finally
oItensVenda.Free;
oProxy.Free;
oItensVenda.Free;
end;
end;
Agora que a aplicação cliente foi implementada, é só testarmos. O resultado do TMemo MJSON deve ser parecido com o da figura 11.
Figura 11: Testando inserção de itens
Pronto. Agora para concluir o nosso artigo deve ser enfatizada a criação de um type TItensProduto, criado na Listagem 1. Todo objeto Generic que for serializado deve se criar um alias ou um type para ele, pois o objeto TJSONMarshal consegue serializar, mas o objeto TJSONUnMarshal não consegue deserializar se não for criado um type ou um alias que aponte para a declaração generic. Ou seja, toda vez que for trafegar um objeto Generic entre aplicações utilizando JSON, deve-se criar um type para apontar para o tipo Generic, como mostra a Listagem 5 e não deve simplesmente declarar a variável, como mostra a Listagem 6, pois provocará um erro no servidor.
Listagem 5: Declaração de um type que referencia um type Generic
Type
TItensVenda = TObjectList<TProduto>
Var
oItensVenda : TItensVenda
Listagem 6: Declaração de uma variável sem a criação de um type
Var
oItensVenda : TObjectList<TProduto>
Conclusão
Como foi visto nesse artigo, não é complicado realizar a troca de classes genéricas entre aplicações Cliente/Servidor. Com esses conceitos que foram adquiridos nesse artigo já podemos ter uma base para o desenvolvimento de aplicações corporativas com um grau ainda maior de complexidade. É bastante simples essa troca de informações entre aplicações.
É importante ressaltar que utilizando estas técnicas de serialização em JSON os nossos servidores já ficam aptos para serem consumidos por aplicações feitas em outras linguagens, como C#, Java, PHP e etc., desde que as mesmas suportem JSON, que praticamente todas suportam.
Ficamos por aqui, e fiquem livres para escreverem comentários, críticas e sugestões para novos artigos. Fiquem com Deus.