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.
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?
@Rafael,
Você poderia usar callback para avisar ao cliente, antes de executar a query execute um callback para o cliente.
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?
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???