Author: Fernando Luiz R1441
Table of Contents
Introdução
Durante o Delphi Tour prometemos falar mais sobre o novo suporte a Reflection no FireDAC para o desenvolvimento de aplicações Multi-tier.
Este novo suporte foi introduzido ainda na versão XE5 Update 2, como você pode observar neste post introdutório do Marco Cantu.
Vamos detalhar um pouco mais este framework e também mostrar como adicionar compressão de dados a este cenário, ideal para conexões “não muito boas” (leia-se conexõe 3G…).
Este demo pretende ilustrar um guia inicial de boas práticas com FireDAC Reflection. Ele também será utilizado em posts futuros mostrando deployment, inclusive para Apache:
Visão Geral do Projeto Exemplo
O exemplo base que utilizaremos aqui encontra-se disponível nos demos do Delphi em “C:UsersPublicDocumentsEmbarcaderoStudio14.0SamplesObject PascalDataSnapFireDACJSONReflect”, mas vamos disponibilizar uma cópia atualizada contendo as adições deste post.
Esta é a estrutura do projeto exemplo:
A aplicação server é criada através do wizard “DataSnap WebBroker Application”. Para o exemplo estamos utilizando uma aplicação “Stand Alone” VCL (a partir do XE6 pode ser também FireMonkey), porém você ainda tem as opções Apache e IIS, além de uma aplicação do tipo “console”.
Nosso servidor vai acessar uma base exemplo do Interbase (EMPLOYEE). Serão basicamente três consultas SQL, as quais serão “exportadas” via JSON para a aplicação cliente. Abaixo você pode observar a conexão FireDAC (FDConnection) e as três consultas (FDQuery). Caso não conheça FireDAC, este webinar pode lhe ajudar: http://forms.embarcadero.com/LA14Q1BRBDEApplicationstotheFuture
Conforme comentado acima, os datasets resultantes serão exportados como JSON para a aplicação cliente. Abaixo segue o código exemplo de uma das funções criadas no servidor, todas as demais seguem o mesmo padrão de codificação:
1 2 3 4 5 6 7 |
function TServerMethods1.GetDepartmentNames: TFDJSONDataSets;<br> begin<br> FDQueryDepartmentNames.Active := False;<br> <br> Result := TFDJSONDataSets.Create;<br> TFDJSONDataSetsWriter.ListAdd(Result, FDQueryDepartmentNames);<br> end; |
Observe que você não precisa se preocupar em converter o DataSet para JSON manualmente, a classe auxiliar “TFDJSONDataSetsWriter” possui um método (Add) que vai automaticamente serializar o DataSet em formato JSON e adicioná-lo ao Result, que por sua vez trata-se de um TFDJSONDataSets (basicamente uma lista de DataSets em formato JSON). Todas estas novas classes estão declaradas na unit “Data.FireDACJSONReflect”.
Por tratar-se de uma lista de DataSets, você pode retornar em um mesmo método diversos DataSets, como por exemplo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
function TServerMethods1.GetDepartmentEmployees(<br> const AID: string): TFDJSONDataSets;<br> begin<br> FDQueryDepartmentEmployees.Active := False;<br> FDQueryDepartmentEmployees.Params[0].Value := AID;<br> <br> FDQueryDepartment.Active := False;<br> FDQueryDepartment.Params[0].Value := AID;<br> <br> Result := TFDJSONDataSets.Create;<br> <br> TFDJSONDataSetsWriter.ListAdd(Result, sDepartment, FDQueryDepartment);<br> TFDJSONDataSetsWriter.ListAdd(Result, sEmployees, FDQueryDepartmentEmployees);<br> end; |
Outro método que gostaria de comentar é o responsável por aplicar as alterações retornadas pelo “client” diretamente no banco de dados:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
procedure TServerMethods1.ApplyChangesDepartmentEmployees(<br> const ADeltaList: TFDJSONDeltas);<br> var<br> LApply: IFDJSONDeltasApplyUpdates;<br> begin<br> LApply := TFDJSONDeltasApplyUpdates.Create(ADeltaList);<br> <br> LApply.ApplyUpdates(sDepartment, FDQueryDepartment.Command);<br> <br> if LApply.Errors.Count = 0 then<br> LApply.ApplyUpdates(sEmployees, FDQueryDepartmentEmployees.Command);<br> <br> if LApply.Errors.Count > 0 then<br> raise Exception.Create(LApply.Errors.Strings.Text);<br> end; |
A aplicação cliente envia um “TFDJSONDeltas” para o server. Basicamente temos que instanciar um “IFDJSONDeltasApplyUpdates” e aplicar as alterações nos Datasets originais através do método “ApplyUpdates”.
Para a aplicação cliente, qualquer plataforma pode ser utilizada. Ou seja, o código do exemplo se aplica tanto a VCL (Windows 32/64), bem como ao FireMonkey (Windows, OS X, iOS e Android)!
Uma vez criada sua aplicação cliente, você deve utilizar o wizard “DataSnap REST Client Module” para gerar o código do proxy que conterá as chamadas para sua aplicação servidora. Em outras palavras, você cria uma aplicação na plataforma desejada (desktop ou mobile) e utiliza o wizard acima para gerar o código necessário para conectá-la a aplicação servidora.
Estamos utilizando Visual LiveBindings para efetuar a ligação dos controles visuais. Por esta razão é necessário definir os fields dos FDMemTables para então fazer a ligação em tempo de design. Neste caso, o tipo de dado dos campos não é importante, mas o nome deve ser exatamente o mesmo retornado pelo servidor.
Vamos entender agora como fazer a chamada para a aplicação servidora, abaixo temos o código que retorna a lista de departamentos e preenche o TListView “ListViewDepartments”:
1 2 3 4 |
procedure TDepartmentsClientForm.ButtonDepartmentsClick(Sender: TObject);<br> begin<br> GetDepartmentNames;<br> end; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
procedure TDepartmentsClientForm.GetDepartmentNames;<br> var<br> LDataSetList: TFDJSONDataSets;<br> begin<br> try<br> LDataSetList := ClientModule2.ServerMethods1Client.GetDepartmentNames();<br> UpdateDepartmentNames(LDataSetList);<br> except<br> on E: TDSRestProtocolException do<br> HandleRestException(ClientModule2.DSRestConnection1, 'Get Departments error', E)<br> else<br> raise;<br> end;<br> end; |
1 2 3 4 5 6 |
procedure TDepartmentsClientForm.UpdateDepartmentNames(const ADataSetList: TFDJSONDataSets);<br> begin<br> FDMemTableDepartments.Active := False;<br> FDMemTableDepartments.AppendData(<br> TFDJSONDataSetsReader.GetListValue(ADataSetList, 0));<br> end; |
Como podem observar, o método “GetDepartmentNames” faz uma chamada ao servidor (através da classe proxy gerada pelo wizard “DataSnap REST Client Module”) retornando um “TFDJSONDataSets”, ou seja, uma lista de datasets em formato JSON. Resta então, utilizando a classe auxiliar “TFDJSONDataSetsReader”, reverter o processo de serialização, adicionando o resultando em um FDMemTable. O TFDMemTable pode ser comparado a um TClientDataSet, isto é, um dataset em memória. Os dados armazenados em um TFDMemTable podem ser alterados, e retornados ao servidor para atualização no banco de dados, fazendo-se uma chamada ao método correspondente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
procedure TDepartmentsClientForm.ButtonApplyUpdatesClick(Sender: TObject);<br> begin<br> ApplyUpdates;<br> end;<br> <br> function TDepartmentsClientForm.GetDeltas: TFDJSONDeltas;<br> begin<br> Result := TFDJSONDeltas.Create;<br> TFDJSONDeltasWriter.ListAdd(Result, sEmployees, FDMemTableEmployee);<br> TFDJSONDeltasWriter.ListAdd(Result, sDepartment, FDMemTableDepartment);<br> end;<br> <br> procedure TDepartmentsClientForm.ApplyUpdates;<br> var<br> LDeltaList: TFDJSONDeltas;<br> begin<br> LDeltaList := GetDeltas;<br> try<br> ClientModule2.ServerMethods1Client.ApplyChangesDepartmentEmployees(LDeltaList);<br> except<br> on E: TDSRestProtocolException do<br> HandleRestException(ClientModule2.DSRestConnection1, 'Apply Updates error', E)<br> else<br> raise;<br> end;<br> end; |
Otimizando a Transferência dos Dados
Para aplicações onde todas as camadas (server e client) estão desenvolvidas em Delphi ou C++ Builder, podemos ainda aplicar uma compressão de dados para otimizar seu tempo de transferência. Obviamente, a diferença somente será perceptível em um ambiente com alguma restrição de banda (3G por exemplo).
Tanto o Delphi, quanto o C++ Builder, trazem uma implementação da ZLib, biblioteca para compressão de dados: http://docwiki.embarcadero.com/Libraries/XE6/en/System.ZLib. O mais interessante é que nossa implementação da ZLib está disponível para todas as plataformas suportadas, ou seja, você pode compactar uma informação em seu servidor Windows e descompactar em sua aplicação mobile iOS ou Android!
Você pode facilmente adicionar a compressão de dados em seus métodos JSON utilizando os “class methods” que estamos disponibilizando abaixo, os quais tornam o uso da ZLib bastante simples:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
unit DSSupportClasses;<br> <br> interface<br> <br> uses System.Classes, System.ZLib, System.SysUtils;<br> <br> type<br> TDSSupportZLib = class(TObject)<br> private<br> { private declarations }<br> protected<br> { protected declarations }<br> public<br> { public declarations }<br> class function ZCompressString(aText: string): TBytes;<br> class function ZDecompressString(aText: TBytes): string;<br> end;<br> <br> implementation<br> <br> { TDSSupport }<br> <br> class function TDSSupportZLib.ZCompressString(aText: string): TBytes;<br> var<br> strInput: TBytesStream;<br> strOutput: TBytesStream;<br> Zipper: TZCompressionStream;<br> begin<br> SetLength(Result, 0);<br> strInput := TBytesStream.Create(TEncoding.UTF8.GetBytes(aText));<br> strOutput := TBytesStream.Create;<br> try<br> Zipper := TZCompressionStream.Create(TCompressionLevel.clMax, strOutput);<br> try<br> Zipper.CopyFrom(strInput, strInput.size);<br> finally<br> Zipper.Free;<br> end;<br> Result := Copy(strOutput.Bytes, 0, strOutput.size);<br> finally<br> strInput.Free;<br> strOutput.Free;<br> end;<br> end;<br> <br> class function TDSSupportZLib.ZDecompressString(aText: TBytes): string;<br> var<br> strInput: TBytesStream;<br> strOutput: TBytesStream;<br> UnZipper: TZDecompressionStream;<br> begin<br> Result := '';<br> strInput := TBytesStream.Create(aText);<br> strOutput := TBytesStream.Create;<br> try<br> UnZipper := TZDecompressionStream.Create(strInput);<br> try<br> try<br> strOutput.CopyFrom(UnZipper, 0);<br> except<br> on E: Exception do<br> begin<br> raise Exception.Create('Error Message: ' + E.Message);<br> end;<br> end;<br> finally<br> UnZipper.Free;<br> end;<br> Result := TEncoding.UTF8.GetString(strOutput.Bytes, 0, strOutput.size);<br> finally<br> strInput.Free;<br> strOutput.Free;<br> end;<br> end;<br> <br> end. |
Porém, nenhuma implementação adicional será necessária em seu projeto. O que fizemos foi modificar a unit “Data.FireDACJSONReflect”, onde estão implementadas as classes base do FireDAC JSON Reflection, adicionando a compressão/descompressão nos métodos de serialização JSON. Assim, basta adicionar esta versão da unit a seu projeto e fazer um “Build”. Compressão/descompressão adicionadas!
Downloads e referências
-
1Exemplo utilizado acima (incluindo a unit "Data.FireDACJSONReflect" modificada):
-
1<a href="http://cc.embarcadero.com/item/29909">http://cc.embarcadero.com/item/29909</a>
-
-
1Replay do CodeRage 8 (contém sessões sobre FireDAC e DataSnap):
-
1<a href="https://www.youtube.com/playlist?list=PLwUPJvR9mZHiaYvH9Xr7WuFCVYugC4d0w">https://www.youtube.com/playlist?list=PLwUPJvR9mZHiaYvH9Xr7WuFCVYugC4d0w</a>
-
-
1Todos os exemplos do RAD Studio XE6 atualizados:
-
1<a href="http://sourceforge.net/p/radstudiodemos/code/HEAD/tree/branches/RadStudio_XE6/">http://sourceforge.net/p/radstudiodemos/code/HEAD/tree/branches/RadStudio_XE6/</a>
-
EMBARCADERO CONFERENCE BRASIL!!!
Aproveitando a ocasião, já estamos preparando a conferência deste ano, e a página para submissão de palestras já está no ar! Se você tem algo interessante para mostrar, em Delphi e C++ Builder, ou ainda um caso de sucesso utilizando nossas ferramentas, estamos esperando por sua inscrição:
http://www.embarcaderoconference.com.br/palestrantes/
Design. Code. Compile. Deploy.
Start Free Trial Upgrade Today
Free Delphi Community Edition Free C++Builder Community Edition