Dados no dispositivo é uma necessidade!

Você já teve uma app que parou de funcionar em seu smartphone ou tablet porque você ficou sem conexão? Existe algo mais frustrante que perder algo crítico que está quase pronto devido a uma perda de conexão de rede? Por que não ter uma app com dados locais, ou ao menos com um cache de dados locais? Aí você me pergunta: e a segurança desses dados? Já ouviu sobre criptografia AES de 256 bit? Devem ser necessários ao menos 10 bilhões de anos para quebrar esse tipo de esquema!

Então por que não utilizamos mais desta técnica? Qual a razão de nos preocuparmos com perdas de dados como esta onde um laptop foi perdido, ou esta de uma importante companhia da área de saúde, se os dados estivessem propriamente protegidos com um mecanismo de criptografia de 256 bit?

Normalmente “protegemos” nossos dados utilizando um usuário/senha e mantendo os dados remotos… porque não colocar os dados no device e ter uma aplicação mais robusta? Dessa maneira podemos nos livrar do medo da temida 1 barra de conectividade, ou dispositivos perdidos ou roubados. Por que não usar a criptografia AES de 256 bits para proteger o seu bem mais precioso… os seus dados?

Interbase DB é um banco de dados (server ou embedded) que suporta criptografia de 256 bit para proteger campos, linhas ou todo o banco de dados de olhos curiosos indesejados. SQLite por sua vez, não possui este nível de granularidade de criptografia, normalmente comportando-se como um arquivo texto. Interbase ocupa muito pouco espaco (3 MB), tornando-o perfeito para devices, smartphones, tablets, IoT, equipamentos médicos, etc. Por falar nisso, o Interbase atualmente suporta iOS, Android, Windows, Linux e OSX.

A coisa mais legal é que o InterBase usa o mesmo formato de dados através de todas estas plataformas diferentes. Por que é tão legal você pergunta? Você pode literalmente copiar o arquivo de banco de dados do iOS para o Android para o Windows, etc., sem modificar um único bit de dados. Considere o tempo que você vai ganhar testando em diferentes plataformas!

Vamos lá arquitetos de dados e desenvolvedores de software, não tenham medo de colocar os dados no dispositivo com o InterBase DB e usar a criptografia AES de 256 bits para os dados que você não pode se dar ao luxo de perder. Comece por fazer o download da versão de avaliação gratuita e siga a página de webinars e exemplos do Interbase.

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/