Implementando cache de dados em servidores DataSnap

O gerenciamento de memória do DataSnap é muito poderoso e uma característica chave para a implementação de cache. Imagine o cenário onde a aplicação cliente solicita os mesmos dados milhares de vezes durante o dia, certamente teremos que tocar o banco de dados a cada solicitação. Vamos supor que esses dados não mudam com muita freqüência, por exemplo: tabela de países, estados ou cidades.

Quando implementamos cache para este cenário, a primeira solicitação obtém os dados a partir do banco de dados e os mantém em memória, a partir da segunda solicitação o servidor de aplicação irá buscar estes dados em memória e retornar para a aplicação cliente, em outra palavras a aplicação tocou o banco de dados apenas uma única vez.

Cursos Online

A combinação entre o gerenciamento de memória do DataSnap, DBXReader e ClientDataSet é o que você precisa para implementar uma solução de cache.

O gerenciamento de memória do DataSnap é definido através do componente DSServerClass e sua respectiva propriedade LifeCycle, que você pode ser definida como:

  • Server → O servidor mantém uma única instância da classe no server, todos os clientes ao solicitar essa classe receberão sempre a mesma instância (Singleton)
  • Session → O servidor mantém uma instância da classe por sessão do DataSnap, cada cliente recebe uma instância diferente da classe (Statefull)
  • Invocation → A cada execução de um server method uma instância da classe será criada e logo depois destruída (Stateless), você pode intervir no processo de criação e destruição desta classe a partir do servidor.

Vamos ver como podemos implementar uma solução de cache usando o cenário onde terei que armazenar uma lista de estados, esta informação está na tabela de STATE.

A minha classe no servidor (ServerClass) foi criada a partir de um DSServerModule e se chama TDMDataSet5, contém dois métodos privados (private) e um público (public), que são:

private
   function GetRecords(Fields, Table: String): TDBXReader;
   function GetData(Cds : TClientDataSet; Fields, Table: String) : TDBXReader;
public
   function GetState: TDBXReader;

O método GetRecords irá consultar o banco de dados e retornar os registros como um DBXReader, este método não implementa nenhuma lógica para verificar se o cache já está preenchido.

function TDMDataSet5.GetRecords(Fields, Table: String): TDBXReader;
var
    cmd: TDBXCommand;
begin

    cmd := DMServerContainer.GetConnection.DBXConnection.CreateCommand;
    try
      cmd.Text := 'Select ' + Fields + ' from ' + Table;
      Result := cmd.ExecuteQuery;
    except
      raise;
    end;
end;

Como o DBXReader é unidirecional não podemos manter os dados em memória, a solução para isso é copiar os dados e mantê-los em um ClientDataSet.

GetData é um método interno responsável por criar, manter e retornar os dados como DBXReader.

Conforme a implementação abaixo o método irá obter os dados a partir do banco de dados (GetRecords) somente se o ClientDataSet não está ativo, em outras palavras nós nunca tivemos os dados em cache e somente iremos executar este código uma única vez. Após o bloco do IF a classe TDBXDataSetReader irá copiar os dados do ClientDataSet para um DBXReader e retorná-lo.

function TDMDataSet5.GetData(Cds: TClientDataSet; Fields, Table: String): TDBXReader;
var
    Reader : TDBXReader;
begin
    if not Cds.Active then // Not active means, never move the data to ClientDataSet – no cache
      begin
      Reader := GetRecords(Fields, Table);
      TDBXDataSetReader.CopyReaderToClientDataSet( Reader, Cds );
      Reader.Free;
      Cds.Open;
    end;

    Result := TDBXDataSetReader.Create(Cds, False (* InstanceOwner *) );
end;

Você pode estar se perguntando por que estou copiando os dados do ClientDataSet para DBXReader e não retornando diretamente o ClientDataSet, duas razões:

  • Não é possível fazer o marshal/unmarshal de ClientDataSet para um objeto JSON
  • DataSnap converte DBXReader em JSON quando o server methods é chamado a partir de uma interface REST.

Um ponto importante, o DSServerModule TDMDataSet5 irá gerenciar o cache, se o LifeCycle para esta classe for definido como server isso irá significar uma única instância do cache para todos os clientes, podemos chamar isso de “cache global”, caso defina como Session estaremos criando um cache para cada cliente conectado ao servidor.

Caso a sua aplicação cliente utilize o DBXClient irá receber um DBXReader a partir da execução do server method, a partir dai é sua responsabilidade decidir o que fazer com os dados, mas sendo necessário conectar estes dados a componentes data-aware será necessário copiar os dados do DBXReader para um ClientDataSet, o método TDBXDataSetReader.CopyReaderToClientDataSet é a solução pra isso.

TDBXDataSetReader.CopyReaderToClientDataSet(Reader, CDSCity);

Se algum dado relacionado com a tabela State mudar será necessário implementar um server method para atualizar os dados. Além disso, ao definir o LifeCycle como Server o cache será destruído no momento que o servidor parar (Stop), mas se utilizamos o LifeCycle Session o cache será destruído no momento em que a aplicação cliente se desconecta do server.

Usando esta técnica você garante que o cache no servidor não depende da implementação do lado cliente, além disso quero lembrar que essa solução irá funcionar como DataSnap Servers, se você implementou o server usando DataSnap REST Interface você não terá cache, porque cada solicitação ao server funciona como o lifecycle invocation.

Este é um exemplo realista que explica como implementar cache em servidores DataSnap usando dados a partir de um banco de dados como exemplo, além disso, você aprendeu como mover dados de/para ClientDataSet de/para DBXReader.

O download do código fonte utilizado como exemplo está disponível aqui, veja a unit DataSetDM5.pas (Servidor) e FormDataSet5 (Cliente).

Fonte: www.andreanolanusse.com/pt/implementando-cache-de-dados-em-servidores-datasnap por Andreano Lanusse

Share on Facebook0Tweet about this on TwitterShare on Google+0Share on LinkedIn0Print this pageEmail this to someone

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Blog Willian Rodrigues

Receba as atualizações do blog no seu e-mail