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.

Iniciando um novo Projeto

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.

Selecionando o projeto DataSnap

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.

Etapa 1 de 4 na configuração do Servidor

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.

Etapa 3 de 4 na configuração do Servidor

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 nomeNovo Nome
Form1UFViewPrincipal
ServerMethodsUnitUSMMetodos
ServerContainerUnitUSCServidor

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.

PropriedadeTipo
NomeString
ValorDouble

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.

Etapa para adicionar um novo server module

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.

Adicionando no projeto um novo Server Module

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.

Adicionando um projeto cliente

Figura 7: Adicionando um projeto cliente

Adicionando uma aplicação 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 classeNovo Nome
TMemoMJSON
TButtonBInserirItensVenda
TSQLConnectionSQLCServidor

Tabela 3: Nomes dos compontens do form

Organize sua tela para que fique parecido com a Figura 9.

Organização da tela

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.

PropriedadeNovo Valor
DriveDatasnap
Port8565

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.

Gerando Classe Proxy

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.

Testando inserção de itens

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.