Utilizando callback em DataSnap 2010

Solicitação número 1 dos usuários de DataSnap, suporte a callback, DataSnap 2010 permite a execução de server methods utilizando callback, em outras palavras, é esperar que o servidor envie retornos a aplicação cliente durante o processamento. Para exemplicar melhor, imagine os famosos fechamentos de final do mês, estes são disparados a partir de uma aplicação cliente que somente recebe um retorno ao finalizar o processo do lado servidor, com callback podemos notificar a aplicação cliente como está o processamento do lado servidor.
A classe TDBXCallback é responsável pelo callback, assim você deverá criar uma classe descendente, implementar o método execute e passar como parâmetro no seu server method. O servidor irá executar o método Execute do parâmetro de callback enviado ao servidor, na verdade o servidor está notificando o cliente para executar algum código.

Para melhor exemplificar, vamos imaginar que você precisa fazer o backup da sua base de dados a partir de uma instrução enviada pela aplicação cliente para o servidor, durante o processo de backup você deseja saber como está o andamento do backup, esta é uma tarefa para o callback, o código faz exatamento isso, backup de uma base de dados InterBase e retorna o log para a aplicação cliente.

Para começar temos que implementar o server method que será responsável por executar o backup, este obrigatoriamente necessita um parâmetro do tipo TDBXCallback o qual será responsável por enviar a notificação a aplicação cliente através do método Execute.

O método Execute por sua vez recebe envia um TJSONObject o qual contém um TJSONPair com as mensagens do log de backup da base de dados, lembre-se que eu optei por enviar apenas um valor dentro do objeto e a cada linha de log recebida o cliente será notificado que há uma mensagem nova.

procedure TDSServerBatch.StartBackup(sMessage: TDBXCallback;
  sBackupFileName: String);
var
  LCallbackValue: TJSONObject;
  db: String;

begin

  db := DMServerContainer.GetEmployeeDBName;

  srvBackup.DatabaseName := Copy(db, Pos(':', db) + 1, Length(db));

  srvBackup.Attach;
  srvBackup.BackupFile.Add(sBackupFileName);

  srvBackup.ServiceStart;
  if srvBackup.Verbose then
    while not srvBackup.Eof do
    begin

      // if srvBackup.IsServiceRunning then
      begin
        LCallbackValue := TJSONObject.Create;
        LCallbackValue.AddPair(TJSONPair.Create('Server return',
            srvBackup.GetNextLine));

        sMessage.Execute(LCallbackValue);

      end;

    end;

  if srvBackup.Active then
    srvBackup.Detach();

end;

Vamos agora ao lado cliente, como temos que passar um parâmetro do tipo TDBXCallback então teremos que criar e implementar a tal classe. Vem a pergunta, para cada server method que necessita efetuar callback terei que criar uma classe de callback? A resposta é não, neste exemplo utilizaremos anonymous method para evitar isso.

Abaixo a classe de callback que estarei utilizando, veja que o método execute recebe um parâmetro do tipo TDSCallbackMethod o qual é um anonymous method, desta forma na criação da instância desta classe será passado o código a ser executando quando o servidor executar o método Execute.

type
  TDSCallbackMethod = reference to function(const Args: TJSONValue): TJSONValue;

  TMessageCallback = class(TDBXCallback)
  private
    FCallBackMethod: TDSCallbackMethod;

  public
    constructor Create(CallBackMethod: TDSCallbackMethod);
    function Execute(const Arg: TJSONValue): TJSONValue; override;
  end;

implementation

constructor TMessageCallback.Create(CallBackMethod: TDSCallbackMethod);
begin
  FCallBackMethod := CallBackMethod;
end;

function TMessageCallback.Execute(const Arg: TJSONValue): TJSONValue;
begin
  Result := FCallbackMethod(Arg);
end;

Com a classe de callback definida, temos agora que implementar o anonymous method e executar o server method conforme o exemplo a seguir.

Veja que o parâmetro Args do anonymous method é do tipo TJSONValue, extraimos os valores TJSONPair onde se encontra o log do backup.

var
  s : TDSServerBatchClient;
begin

  callback := TMessageCallback.Create( function(const Args: TJSONValue) : TJSONValue

    var
     LJSONObject: TJSONObject; I: Integer;
     LMessage: string;
    begin
      // Extract information about the transformation from Json
      LJSONObject := TJSONObject(Args);
      Result := nil;

      for I := 0 to LJSONObject.Size - 1 do begin
         with LJSONObject.Get(I) do
           LMessage := LMessage + Format('%s ==>> "%s"', [JSonString.Value, JsonValue.Value]);

      // Display information in a listbox
        MMBackupLog.Lines.Add(LMessage);
        MMBackupLog.Update;
        Result := TJSONTrue.Create;

      end;
    end);

Na sequência apenas executamos o método, passando a classe callback com parâmetro e o anonymous method será responsável por adicionar a mensagem de retorno no campo memo que está na tela, utilizado neste exemplo.

  s := TDSServerBatchClient.Create(DMClientContainer.MyDSServer.DBXConnection);
  s.StartBackup(callback, 'mybackup.ibk');

Espero que este post tenha esclarecido as dúvidas relacionadas a callback.

Download do código fonte.

4 respostas
  1. Rafael Soares
    Rafael Soares says:

    Boa noite,
    Hoje tenho alguns que retornam TJSONArray para o cliente, TJSONArray este que é alimentado através de uma consulta ao banco de dados. Pergunta: Como poderia adaptar esta técnica para avisar o cliente que o servidor ainda está processando a consulta?

    Responder
  2. Júlio César Ferreira (@jcmferreira)
    Júlio César Ferreira (@jcmferreira) says:

    Bom dia Andreano!

    Muito bom esse recurso de callbacks! Estou com uma dúvida…

    No teste que estou tentando realizar, a aplicação disponibiliza um cadastro de empresas para todos os clientes. Quando um dos clientes acessa uma empresa qualquer, a aplicação servidor vai atualizar as tabelas do banco de dados dessa empresa.

    A dúvida: Como fazer para que outros clientes que tentem acessar essa mesma empresa recebam um aviso de que ela está sendo atualizada por outro cliente e bloquear esse acesso?

    É possível fazer isso com callback?

    Responder
  3. Denis
    Denis says:

    No DataSnap antigo eu conseguia registrar o clientcallback uma vez e depois sempre enviar mensagens por ele. Neste novo só estou conseguindo fazer funcionar passando como um parâmetro da função. Ou seja, se quiser fazer um log dos processamentos do servidor, por exemplo, vou ter que colocar esse parâmetro em todas as minhas funções!!!??. 🙁

    Quando tento passar o parâmetro por uma função, por exemplo: RegistreCallBack e salvo o parâmetro em uma variável. Ao tentar escrever no callback em outro método, obtenho o erro:
    “Remote error: A callback parameter cannot be found at the provided index”

    Me ajude aí, o novo é pior que o anterior???

    Responder

Deixe uma resposta

Want to join the discussion?
Feel free to contribute!

Deixe uma resposta

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


Esse site utiliza o Akismet para reduzir spam. Aprenda como seus dados de comentários são processados.