Multi-tier com Delphi XE6 e FireDAC JSON Reflection

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:

function TServerMethods1.GetDepartmentNames: TFDJSONDataSets;
begin
 FDQueryDepartmentNames.Active := False;

 Result := TFDJSONDataSets.Create;
 TFDJSONDataSetsWriter.ListAdd(Result, FDQueryDepartmentNames);
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:

function TServerMethods1.GetDepartmentEmployees(
  const AID: string): TFDJSONDataSets;
begin
  FDQueryDepartmentEmployees.Active := False;
  FDQueryDepartmentEmployees.Params[0].Value := AID;

  FDQueryDepartment.Active := False;
  FDQueryDepartment.Params[0].Value := AID;

  Result := TFDJSONDataSets.Create;

  TFDJSONDataSetsWriter.ListAdd(Result, sDepartment, FDQueryDepartment);
  TFDJSONDataSetsWriter.ListAdd(Result, sEmployees, FDQueryDepartmentEmployees);
end;

Outro método que gostaria de comentar é o responsável por aplicar as alterações retornadas pelo “client” diretamente no banco de dados:

procedure TServerMethods1.ApplyChangesDepartmentEmployees(
  const ADeltaList: TFDJSONDeltas);
var
  LApply: IFDJSONDeltasApplyUpdates;
begin
  LApply := TFDJSONDeltasApplyUpdates.Create(ADeltaList);

  LApply.ApplyUpdates(sDepartment, FDQueryDepartment.Command);

  if LApply.Errors.Count = 0 then
    LApply.ApplyUpdates(sEmployees, FDQueryDepartmentEmployees.Command);

  if LApply.Errors.Count > 0 then
    raise Exception.Create(LApply.Errors.Strings.Text);
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”:

procedure TDepartmentsClientForm.ButtonDepartmentsClick(Sender: TObject);
begin
  GetDepartmentNames;
end;
procedure TDepartmentsClientForm.GetDepartmentNames;
var
  LDataSetList: TFDJSONDataSets;
begin
  try
    LDataSetList := ClientModule2.ServerMethods1Client.GetDepartmentNames();
    UpdateDepartmentNames(LDataSetList);
  except
    on E: TDSRestProtocolException do
      HandleRestException(ClientModule2.DSRestConnection1, 'Get Departments error', E)
    else
      raise;
  end;
end;
procedure TDepartmentsClientForm.UpdateDepartmentNames(const ADataSetList: TFDJSONDataSets);
begin
  FDMemTableDepartments.Active  := False;
  FDMemTableDepartments.AppendData(
    TFDJSONDataSetsReader.GetListValue(ADataSetList, 0));
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:

procedure TDepartmentsClientForm.ButtonApplyUpdatesClick(Sender: TObject);
begin
  ApplyUpdates;
end;

function TDepartmentsClientForm.GetDeltas: TFDJSONDeltas;
begin
  Result := TFDJSONDeltas.Create;
  TFDJSONDeltasWriter.ListAdd(Result, sEmployees, FDMemTableEmployee);
  TFDJSONDeltasWriter.ListAdd(Result, sDepartment, FDMemTableDepartment);
end;

procedure TDepartmentsClientForm.ApplyUpdates;
var
  LDeltaList: TFDJSONDeltas;
begin
  LDeltaList := GetDeltas;
  try
    ClientModule2.ServerMethods1Client.ApplyChangesDepartmentEmployees(LDeltaList);
  except
    on E: TDSRestProtocolException do
      HandleRestException(ClientModule2.DSRestConnection1, 'Apply Updates error', E)
    else
      raise;
  end;
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:

unit DSSupportClasses;

interface

uses System.Classes, System.ZLib, System.SysUtils;

type
  TDSSupportZLib = class(TObject)
  private
    { private declarations }
  protected
    { protected declarations }
  public
    { public declarations }
    class function ZCompressString(aText: string): TBytes;
    class function ZDecompressString(aText: TBytes): string;
  end;

implementation

{ TDSSupport }

class function TDSSupportZLib.ZCompressString(aText: string): TBytes;
var
  strInput: TBytesStream;
  strOutput: TBytesStream;
  Zipper: TZCompressionStream;
begin
  SetLength(Result, 0);
  strInput := TBytesStream.Create(TEncoding.UTF8.GetBytes(aText));
  strOutput := TBytesStream.Create;
  try
    Zipper := TZCompressionStream.Create(TCompressionLevel.clMax, strOutput);
    try
      Zipper.CopyFrom(strInput, strInput.size);
    finally
      Zipper.Free;
    end;
    Result := Copy(strOutput.Bytes, 0, strOutput.size);
  finally
    strInput.Free;
    strOutput.Free;
  end;
end;

class function TDSSupportZLib.ZDecompressString(aText: TBytes): string;
var
  strInput: TBytesStream;
  strOutput: TBytesStream;
  UnZipper: TZDecompressionStream;
begin
  Result := '';
  strInput := TBytesStream.Create(aText);
  strOutput := TBytesStream.Create;
  try
    UnZipper := TZDecompressionStream.Create(strInput);
    try
      try
        strOutput.CopyFrom(UnZipper, 0);
      except
        on E: Exception do
        begin
          raise Exception.Create('Error Message: ' + E.Message);
        end;
      end;
    finally
      UnZipper.Free;
    end;
    Result := TEncoding.UTF8.GetString(strOutput.Bytes, 0, strOutput.size);
  finally
    strInput.Free;
    strOutput.Free;
  end;
end;

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

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/

Advertisements

20 thoughts on “Multi-tier com Delphi XE6 e FireDAC JSON Reflection

  1. Tentei fazer um aplicativo com essas características, usei o código fonte do exemplo de base, mas na minha aplicação após executar a função requerendo os dados da Tabela aparece o seguinte erro:
    —————————
    Get Departments error: [FireDAC][Stan]-716. Unknown storage format [BIN]. Hint: To register it, you can drop component [TFDStanStorageXxxLink] into your project
    —————————
    OK
    —————————

    Like

  2. Faltou adicionar este formato de storage ao seu projeto. Verifique entre os componentes do FireDAC e adicione o “TFDStanStorageBinLink”.

    Like

  3. Fernando conforme contato anterior estou com dificuldades para inserir ou atualizar registros. Segui o modelo do exemplo mas mesmo assim não obtive sucesso. Enviei email com o fonte. Aguardo sua ajuda.

    Like

  4. Fernando, he seguido el ejemplo con tablas que tienen más de 100 registros y se presenta la particularidad de que solo se muestran desde el registro 51 …? los primeros 50 no aparecen. Estuve viendo en el lado Server y se aprecia que FireDAC trae los registros en paquetes de 50, puede estar allí el problema …? O es un tema de configuración o parametrización de componentes …?

    Like

  5. Prezado Fernando, boa noite. Estou com a mesma dúvida do amigo GustavoSV, fiz um teste seguindo o post do nobre colega Adriano Santos efetuando um select com 295 registros e só não aparecem os primeiros 50. Saberia informar por que?

    Like

  6. No método ApplyChangesDepartmentEmployees consigo manipular os dataset que está no delta?
    Tipo, verificar o valor de algum campo que veio no dataset para executar algo

    Like

  7. Fernando, tenho a mesma dúvida do Dener:
    ‘E se eu quiser que retorne apenas JSON puro ? Quero consumir esses dados em jQuery por exemplo. Obrigado !’.

    Like

  8. Eu analisei o sample ‘FireDACJSONReflect’ como foi sugerido por você, e nele tem a função ‘GetDepartmentNamesJSON’ que retorna um TJSONObject. Quando eu acesso pelo browser ‘http://localhost:8080/datasnap/rest/TServerMethods1/GetDepartmentNamesJSON’ ele retorna desta forma ‘{“result”:[{“FDQueryDepartmentNames”:”QURCUw4AAABYAwAA/wABAAH/Av8D…”}]}. Gostaria de saber como faço para ele retornar o json assim:

    [{
    “id”:”1″,
    “codigobarras”:”789324353″,
    “descricao”:”PRODUTO TESTE 1″,
    “preco”:”60″,
    “estoque”:”9″,
    “cnpjloja”:”62611423000103″
    }].

    Fazendo uma analogia com o PHP que também estou estudando, eu faço isso com json_encode e tenho o retorno desta forma. Estou desenvolvendo uma aplicação com o Delphi, onde envio este json para um servidor php.

    Like

  9. Fernando,

    estou fazendo alguns testes com o TFDMemTable e está acontecendo o seguinte:
    Realizei diversas consultas, com SQL diferente, diretamente no banco de dados, utilizando uma ferramenta de consulta, esta me retornam uma quantidade de registros, porém, quando utilizo a técnica descrita por você utilizando o TFDMemTable, as mesmas consultas me retornam uma quantidade inferior de registros, é alguma configuração do TFDMemTable? O que pode estar acontecendo?

    Obrigado

    Like

  10. Nao estou conseguindo gravar. Creio que seja por causa da constante sUsuarios. Nao sei qual a regra para o valor da connstante.

    LApply.ApplyUpdates(sUsuarios, qryCadBasico.Command);

    Veja a estrutura de meus dados:
    object qryCadBasicousu_id: TIntegerField
    FieldName = ‘usu_id’
    Origin = ‘usu_id’
    Required = True
    end
    object qryCadBasicousu_nome: TStringField
    FieldName = ‘usu_nome’
    Origin = ‘usu_nome’
    Required = True
    Size = 200
    end
    object qryCadBasicousu_nivel: TSmallintField
    FieldName = ‘usu_nivel’
    Origin = ‘usu_nivel’
    Required = True
    end
    object qryCadBasicousu_login: TStringField
    FieldName = ‘usu_login’
    Origin = ‘usu_login’
    Required = True
    end
    object qryCadBasicousu_pwd: TStringField
    FieldName = ‘usu_pwd’
    Origin = ‘usu_pwd’
    Required = True
    Size = 10
    end
    object qryCadBasicousu_dat_cri: TSQLTimeStampField
    FieldName = ‘usu_dat_cri’
    Origin = ‘usu_dat_cri’
    Required = True
    end
    object qryCadBasicousu_dat_alt: TSQLTimeStampField
    FieldName = ‘usu_dat_alt’
    Origin = ‘usu_dat_alt’
    Required = True
    end

    O que preciso fazer?

    Like

  11. Já encontrei a solução. É preciso colocar como True a propriedade AutoCommitUpdate do objeto FDMemTable da aplicação cliente.

    Like

  12. Boa tarde Fernando, tudo bem?

    Estou necessitando de um auxilio em um processo que estou pretendendo implementar.
    Eu criei um app que faz acesso a um servidor cloud via DataSnap Rest Application
    utilizando o FireDac JSON Reflection. Este procedimento funciona perfeitamente
    em meu ambiente.

    A sincronização do servidor para o App será salvo em um banco SQlLIte, que receberá e
    enviará dados. O FireDac JSON Reflection resolveu isso com muita rapidez e agilidade na
    programação. Se mostrou muito rápido e eficiente na sincronização.

    A minha sincronização não será feita em Real-Time, ou seja, eu não estarei conectado ao
    banco de dados do cloud a todo o momento enviando os meus dados. Por problemas de banda larga
    móvel na minha regiaão, combinamos de fazer a sincronização dos dados no início ou no final do
    período.

    Porém a idéia é que neste servidor haja vários bancos de dados de clientes diferentes,
    e o app acessaria o banco a que lhe pertence. A minha idéia é ter apenas um serviço rodando,
    e dinamicamente eu consiga alterar as configurações de acesso do FDConnection.

    Como dito eu consegui efetuar os testes de sincronização apenas quando eu coloco as
    informações do banco de dados fixas em seus parâmetros.

    Lendo o DocWiki da Embarcadero vi que ele suporta três tipos de definições de conexão:
    Persistent, Private e Temporary. No meu caso está funcionando o processo Temporary.Não
    consegui implementar as demais definições.

    Com isso eu gostaria de saber se este cenário que apresentei é possível de ser implementado?
    O que estou fazendo é inviável? Eu teria que ter vários serviços rodando com cada banco sendo
    conectado fixamente?

    Parabéns pelo post e agradeço se conseguir me dar uma diretriz para este caso. Abraço!

    Like

  13. As funções com retorno em TFDJSONDataSets não funcionam no IOS 8 ou maior, vocês sabem o motivo ? existe previsão de correção ?

    Obrigado.

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s