Introdução
As aplicações corporativas estão em constante crescimento, tanto na quantidade de código como na sua complexidade. E é devido a esse crescimento que às vezes ou quase sempre evoluem de forma assustadora, praticamente de forma exponencial. Com isso, a quantidade de parâmetros nos métodos expostos para consumo também tendem a crescer, mas ficam praticamente inviáveis, pois isso resultaria em problemas como legibilidade e manutenção futura, surgindo assim o tão famoso Bad Smell (Mau Cheiro).
Uma forma de contornar esse problema de métodos super populados por parâmetros é encapsular esses parâmetros em uma classe, desde que esses parâmetros façam parte do mesmo contexto e que levem ao mesmo objetivo.
Outra situação em que é muito comum a necessidade de trasfegar classes entre aplicações Cliente/Servidor é para persistir dados em um banco de dados. Imagine um cenário em que se tem a necessidade de inserir no banco de dados as informações de uma pessoa, informações essas que podem ser mais de dez campos. Como vamos fazer essa inserção? Passando todos esses dez valores por parâmetro em um método? E como foi visto anteriormente, essa não seria a solução mais adequada. Mas qual seria a melhor solução? Novamente a resposta é: encapsular esses campos ou parâmetros em uma classe, e reforçando mais uma vez: Desde que esses parâmetros expressem um mesmo contexto, que é o nosso caso.
Não é muito difícil de encontrar um exemplo de Bad Smell, como esse que foi citado acima, um método super populado de parâmetros. Confira na Listagem 1 um exemplo desse mau cheiro, que para muitos pode ser besteira, mas que no futuro vai trazer uma tremenda dor de cabeça.
Não posso deixar de ressaltar que o foco desse artigo é a Transferência de Dados entre Aplicações Cliente/Servidor, mas que vai falar também de Boas e melhores práticas ou qualquer outro assunto que seja necessário para o entendimento desse artigo.
Listagem 1: Bad Smell em método super populado de parâmetros
procedure TCliente.pSalvarDadosCliente(pCodigo, pNome, pEndereco, pCidade, pUF, pBairro, pCEP, pEMail, pCPF, pRG : string; pDataNascimento : TDateTime)
begin
//Código Qualquer
end;
A Listagem 1 contém um método responsável por Salvar essas informações, mas a forma em que serão salvos os dados não é o foco desse artigo. Esse método tem apenas 10 parâmetros, mas é fácil de encontrar métodos com até 20 parâmetros e à medida que a aplicação for evoluindo, a complexidade vai aumentando e a falta de refatoração podem lhe deixar com os cabelos em pé.
Vamos à 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. Clique 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 Application. Como mostra a figura 5.
Figura 3: Etapa 01 de 04 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. Nesse artigo foi escolhida a 8565.
Figura 4: Etapa 03 de 04 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: Nome das Units do Projeto
Vamos imaginar uma situação real que acontece no dia a dia do desenvolvedor. Vamos ter que fazer um cadastro de Clientes.
Antes de fazer qualquer coisa, é preciso fazer uma pequena análise do nosso projeto, pois muitos dos nossos problemas e dor de cabeça quando temos que resolver um bug no sistema se dá pelo fato de não perdermos alguns minutinhos antes de iniciar um projeto. E isso possivelmente resultará em perdas de horas e às vezes até dias, simplesmente pelo fato de não tirarmos esse tempinho para meditar no nosso problema, para tomarmos a melhor decisão a respeito do que e como vamos fazer tal coisa.
Vale salientar claro que não vamos nos aprofundar nessa análise, criando diagramas de casos de uso, diagramas de classe, diagramas de atividades e etc., pois esse não é o foco desse post. Mas é necessário falar um pouco sobre a engenharia de requisitos, pois é muito útil para tomarmos a melhor decisão.
A primeira pergunta que devemos ter nessa situação é: Como vamos expor essas informações? Será criado um método com alguns parâmetros e enviar esses dados para o servidor? Ou vou criar uma classe para encapsular essas informações e criar um único parâmetro onde serão enviados esses dados por meio de uma classe?
A resposta dessas perguntas parece óbvia: com certeza vamos encapsular todas essas informação em uma casse, pois todos os campos expressam um mesmo contexto e levam a um mesmo objetivo que é: Informar os dados pessoais de um determinado cliente.
Então vamos criar uma classe chamada TCliente com as seguintes propriedades e os seguintes tipos como mostra a Tabela 2.
Propriedade | Tipo |
Nome | String |
DataNascimento | TDataTime |
Telefone | String |
Endereco | String |
Bairro | String |
CEP | String |
Cidade | String |
Estado | String |
Tabela 2: Nome das propriedades da classe TCliente
O resultado deve ficar parecido com a Listagem 2.
Listagem 2: Classe TCliente
TCliente = class
private
prv_sNome : string;
prv_dtDataNascimento : TDateTime;
prv_sTelefone : string;
prv_sEndereco : string;
prv_sBairro : string;
prv_sCEP : string;
prv_sCidade : string;
prv_sEstado : string;
public
property Nome : string read prv_sNome write prv_sNome ;
property DataNascimento : TDateTime read prv_dtDataNascimento write prv_dtDataNascimento;
property Telefone : string read prv_sTelefone write prv_sTelefone;
property Endereco : string read prv_sEndereco write prv_sEndereco;
property Bairro : string read prv_sBairro write prv_sBairro;
property CEP : string read prv_sCEP write prv_sCEP;
property Cidade : string read prv_sCidade write prv_sCidade;
property Estado : string read prv_sEstado write prv_sEstado;
end;
Como foi visto na Listagem 2, as propriedades estão acessando diretamente as variáveis sem a necessidade de métodos Gets e Sets, pois o nosso exemplo é bastante simples. Mas provavelmente essas variáveis teriam intermediários, para que possamos fazer críticas e formatações, mas como esse não é o foco desse artigo, as propriedades serão acessadas diretamente.
A classe TCliente será apenas um encapsulador no nosso exemplo, mas isso não impede de termos métodos na nossa classe. E também será ressaltado que acessar as variáveis diretamente pelas propriedades ou criar métodos intermediários como Gets e Sets não vão mudar o comportamento, vai funcionar da mesma forma, pois o que vai ser enviado ou serializado serão as variáveis e não o esqueleto da classe em si.
Uma pergunta que você provavelmente deve está se fazendo: Como é possível trocar ou enviar objetos da aplicação cliente para a aplicação servidor e vice-versa? Para ser possível esse envio, é necessário serializar os nossos objetos que queremos enviar. O que é serializar objetos? Serializar objetos nada mais é do que o processo de armazenamento de um objeto, incluindo os atributos ou campos públicos e privados para stream (fluxo), ou seja, transformar em arquivo binário, XML, JSON, etc. E o processo inverso é chamado de deserialização de objetos, que é transformar esse stream em objetos.
Observação: não se preocupem, pois veremos nesse artigo os dois processos, serialização e deserialização de objetos.
Um fator muito importante que se tem que frisar é a organização das pastas do nosso projeto. Esse projeto está sendo organizado da seguinte forma, como mostra a Figura 7.
Figura 5: Organização das pastas do projeto
Uma pasta onde estão os arquivos referentes ao cliente, outra referente ao servidor e uma terceira pasta referente aos arquivos que são comuns tanto no servidor como no cliente. No nosso caso, a unit que contém a classe TCliente deve ser salva dentro dessa pasta, pois vamos ter que serializar essa classe no cliente e deserializar a mesma classe no servidor e, para isso, é preciso utilizaála. Vamos salvar dentro dessa pasta Comuns a nossa unit com o nome UCliente.
Agora vamos adicionar um novo Server Module em File -> New -> Other, como mostra a Figura 6.
Figura 6: Etapa para adicionar um novo server module
Selecione a pasta DataSnap Server e depois clique duas vezes em Server Module como mostra a Figura 7.
Figura 7: Adicionando no projeto um novo Server Module
Vamos alterar a propriedade Name do Server Module para SMCliente e salvar a Unit com o nome USMCliente.
Feito isso, vamos por a mão na massa para o que realmente interessa. Vamos primeiramente programar a deserialização no servidor no Server Module SMCliente.
Vamos adicionar as units Data.DBXJSON , Data.DBXJSONReflect, para quem tem o Delphi a partir da versão XE2 e para as demais versões adicionar a Unit DBXJSON e a DBXJSONReflect, também sem esquecer de adicionar a unit UCliente.
Vamos adicionar um método chamado pInserirCliente com a seguinte definição, como mostra a Listagem 3.
Listagem 3: Definição do método pInserirCliente
procedure pInserirCliente(pCliente : TJSONValue);
O Parâmetro pCliente é o Objeto serializado, então vamos fazer o processo de deserialização como mostra a Listagem 4.
Listagem 4: Implementação do método pInserirCliente
var UnMarshal : TJSONUnMarshal;
oCliente : TCliente;
begin
if pCliente 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
oCliente := TCliente(UnMarshal.Unmarshal(pCliente));
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 9). Selecione a pasta Delphi Project e depois clique duas vezes em VCL Forms Application, como mostra a Figura 8.
Figura 8: Adicionando um projeto cliente
Figura 9: 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 | BSet |
TSQLConnection | SQLCServidor |
Tabela 3: Nomes dos componentes do form
Organize sua tela para que fique parecida com a Figura 10.
Figura 10: Organização da tela
Vamos fazer as devidas configurações para que o SQLCServidor possa se conectar com a aplicação servidor.
Observação: Não entraremos em 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 client classes, como mostra a Figura 11.
Figura 11: 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 BGet, como mostra a Listagem 5.
Listagem 5: Implementação do evento click do TButton BSet
procedure TFViewPrincipal.BSetClick(Sender: TObject);
var Marshal : TJSONMarshal;
oCliente : TCliente;
oObjetoJSON : TJSONValue;
oProxy : TSMClienteClient;
begin
{$REGION 'Passando valores para o Objeto Cliente'}
oCliente := TCliente.Create;
oCliente.Nome := 'Welson Play';
oCliente.DataNascimento := StrToDateTime('01/10/1993');
oCliente.Telefone := '(85)3333-3333';
oCliente.Endereco := 'Rua da Fé';
oCliente.Bairro := 'Carlito Pamplona';
oCliente.CEP := '60311-730';
oCliente.Cidade := 'Fortaleza';
oCliente.Estado := 'Ceará';
{$ENDREGION}
if Assigned(oCliente) then //Verificando se o objeto foi criado
begin
Marshal := TJSONMarshal.Create; //Instanciando o objeto responsável por serializar
try
oObjetoJSON := Marshal.Marshal(oCliente);//Serializando de fato o objeto oCliente para JSON
finally
Marshal.Free; //Liberando o serializador
end;
end
else
//Caso o Objeto oCliente 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 := TSMClienteClient.Create(SQLCServidor.DBXConnection);
try
//Retornando o Objeto serializado para o Memo
MJSON.Text := oObjetoJSON.ToString;
oProxy.pInserirCliente(oObjetoJSON); //Enviando o Objeto para o Servidor
finally
oProxy.Free; //Liberando o Proxy
end;
end;
A implementação do evento click do TButton BSet é bem simples, como mostrou a Listagem 5. Mas antes temos que adicionar as seguintes Units: Data.DBXJSON , Data.DBXJSONReflect para quem tem o Delphi a partir da versão XE2 e para as demais versões, as units DBXJSON e a DBXJSONReflect, também sem esquecer de adicionar a unit UCliente.
Agora que o evento Click do TButton BSet já foi implementado, vamos testar.
Está sendo passado para a propriedade Text do Memo o objeto serializado, que deve ser algo parecido com a Figura 12.
Figura 12: Serialização do Objeto Cliente
Após visualizar os dados no TMemo MJSON, provavelmente você recebeu um erro no servidor parecido com esse mostrado na Figura 15. Não se assuste se esse erro ocorrer, segundo o fórum da Embarcadero, isso ocorre pelo fato do Delphi não ter encontrado a sua classe via Rtti. Para resolver esse problema é só adicionar antes de deserializar o objeto no servidor o seguinte código: CTX.GetType(TypeInfo(TCliente)), ficando a implementação do método pInserirCliente no servidor, como na Listagem 6.
Observação: O fórum da Embarcadero onde tem a solução para esse problema está no seguinte endereço: http://qc.embarcadero.com/wc/qcmain.aspx?d=86158.
Figura 13: Erro de Rtti no Servidor
Listagem 6: Nova implementação do método pInserirCliente
var UnMarshal : TJSONUnMarshal;
oCliente : TCliente;
CTX : TRttiContext;
begin
CTX.GetType(TypeInfo(TCliente));
if pCliente 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
oCliente := TCliente(UnMarshal.Unmarshal(pCliente));
finally
UnMarshal.Free; //Libera o Objeto da memoria
end;
end;
Não se esquecendo de adicionar a unit System.Rtti para quem tem o Delphi do XE2 acima e para as outras versões, simplesmente Rtti.
Como sempre o Delphi nos ajuda, pois ele já faz todo esse processo que fizemos anteriormente sem precisar de praticamente código nenhum, basta o parâmetro ser a própria classe e ele faz implicitamente, ou seja, nos bastidores, todo esse processo. Mas sempre é bom conhecer o que é feito nos bastidores, pois o Delphi não consegue converter todos os tipos e quando ele não conseguir é necessário alguns pequenos ajustes, principalmente quando se quer enviar um tipo Generic para o servidor ou vice-versa.
Conclusão
Como foi visto nesse artigo, não é complicado realizar a troca de classes 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 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 consumidas por aplicações feitas em outras linguagens, como C#, Java, PHP e etc., desde que esta suporte JSON (praticamente todas suportam).
Ficamos por aqui, e fiquem livres para escrever comentário, críticas e sugestões para novos artigos. Fiquem com Deus.