Fórum Transações em um Servidor Datasnap em conjunto com Operações com ClientDataSets #404352

05/07/2011

0

Estou utilizando a versão XE, e no
momento estou implementando um servidor de aplicação usando a arquitetura
DataSnap. Um impasse que estou enfrentando é o seguinte: 

No servidor de aplicação estão
implementadas as regras de negócio através de métodos (Server Methods) com seus
respectivos parâmetros, deixando todo o controle transacional encapsulado, e
transparente à aplicação cliente. Ou seja, a aplicação cliente consumirá o
método, sem precisar realizar controle transacional, e nem também passar
instruções SQL. 

Na aplicação cliente faço uso de
recursos da VCL como os ClientDataSets ligados a DataSetProviders no servidor
de aplicação, bem como dos controles Dataware (DBedit, DBgrid, etc), pois o
ganho de produtividade e recursos de uma interface gráfica rica não podem ser
desconsiderados. 

No entanto, existem situações onde é
preciso envolver as operações de ClientDatasets (ApplyUpdates) em uma
transação, mas estas estão encapsuladas no servidor. Não sei se estou sendo
claro, mas o que eu desejo é colocar as operações realizadas no(s) ClientDataset(s)
 dentro de um contexto transacional do
servidor de aplicação onde outras rotinas referentes à regra de negócio em
questão estariam implementadas. Com isto, se teria de uma maneira simples a
consistência entre os dados apresentados na aplicação ao usuário e o estado do
Banco de Dados.

Pesquisei na web, e este impasse tem
sido relatado por alguns. Acredito que solução pode estar em passar o Delta dos
ClientDataSets envolvidos para aplicação servidora e esta através dos
respectivos DataSetProviders realize o ApplyUpdates. (Ex:
TDataSetProvider.ApplyUpdates(Delta, MaxErrors, ErrorCount);). Fiz uns testes e
ocorreram alguns erros na chamada do método e passagem do parâmetro do tipo
Delta. Porém, acho que seja preciso serializar os Deltas (OleVariant) para
passá-los como parâmetro para aplicação servidora. Este exemplo do Erick Sasse
mostra uma solução usando Remote Objects SDK, talvez o caminho seja por aí:

 http://www.ericksasse.com.br/applying-updates-to-more-than-one-clientdataset-in-a-single-transaction/

 É isso aí pessoal! Desafio lançado!
Pedro Neto

Pedro Neto

Responder

Posts

05/07/2011

Emerson Nascimento

crie, no servidor, um método para iniciar a transação, outro para finalizar a transação e um último para desfazer as alterações.

então, no cliente, você faz algo assim:

try
   Servidor.AbreTransacao; // método no servidor

   cds1.ApplyUpdates(-1); // cds no cliente
   cds2.ApplyUpdates(-1); // cds no cliente
   cds3.ApplyUpdates(-1); // cds no cliente

   Servidor.ConfirmaTransacao; // método no servidor
except
   on E:Exception do
   begin
      ShowMessage('Erro ao executar procedimento. Msg original: '+E.Message);
      Servidor.CancelaTransacao; // método no servidor
   end;
end;

implemente o ReconcileError de cada Dataset para levantar uma exceção caso encontre algum problema e veja se dá certo.


Responder

Gostei + 0

05/07/2011

Pedro Neto

Emerson,

Desta maneira eu estou deixando que o Cliente manipule o contexto transacional, e isto é justamente o que quero evitar. O Controle transacional é encapsulado no Servidor de aplicação. O cliente só consumirá as Regras de Negócio através de Server Methods. O que quero fazer é combinar as ações dos CS com a regras de negócio implementadas no Servidor. Isto evitaria a necessidade de fazer um fetch nos dados para atualizar o CS após chamar uma regra no servidor que realizasse as alterações no BD.

Responder

Gostei + 0

05/07/2011

Emerson Nascimento

então não entendi essa parte.

Não sei se estou sendo claro, mas o que eu desejo é colocar as operações realizadas no(s) ClientDataset(s)  dentro de um contexto transacional do servidor de aplicação onde outras rotinas referentes à regra de negócio em questão estariam implementadas. Com isto, se teria de uma maneira simples a consistência entre os dados apresentados na aplicação ao usuário e o estado do Banco de Dados.


você pode explicar melhor esse seu caso ?
Responder

Gostei + 0

05/07/2011

Pedro Neto

Bom, vamos lá, e por partes.

Primeiro. Eu tenho um servidor de aplicação DataSnap, nele eu tenho um método (não acessível pelo cliente) que faz o controle transacional (com um bloco try... finally), inclusive com um parâmetro do tipo TProc (método anônimo). Quando implemento uma regra de negócio, faço-a no servidor de aplicação, executando instruções SQL através de um objeto TDBXCommand numa rotina passada como parâmetro para o método anônimo que controla a transação. Até aqui eu expliquei como funciona internamente no servidor de aplicação.
A aplicação cliente irá consumir esta regra de negócio através de um método publicado servidor de aplicação pela arquitetura DataSnap (p.ex.: uso do componente TSQLServerMethod) passando os parâmetros necessários.
Vamos a um exemplo prático:
Imagine que eu tenha um Pedido com Itens para serem atendidos. Neste caso, eu tenho 2 ClientDataSets envolvidos: um para o Pedido e outro para os seus Itens. Para atendê-lo, eu informo as quantidades atendidas para cada item e ao confirmar o atendimento, o sistema deve mudar o campo situação do pedido para atendido, e para cada item do pedido ele deve ir em outra tabela,  "itens padrão", e atualizar o saldo dos itens envolvidos.
Porém além disto, neste atendimento eu tenho outras regras de negócio envolvidas que integram outros módulos do sistema, como por exemplo: registrar dados da contabilidade, ataulizar o orçamento da empresa, registrar a movimentação de saída, etc.
Estas regras de negócio já estariam implementadas no servidor de aplicação, devidamente transacionadas, seguindo um padrão que deixaria a arquitetura da solução muito mais organizada. 

O Impasse está aí: Eu quero combinar as ações dos ClientDataSets (Mudança de situação do Pedido, atendimento do item) com a regra de negócio no servidor. Em outras palavras, eu poderia implementar tudo no servidor, mas como atualizar as informações de maneira prática na tela do usuário, o que quero é colocar tudo em contexto transacional.

Espero ter sido claro.
Responder

Gostei + 0

05/07/2011

Emerson Nascimento

mas foi isso mesmo que eu entendi.

vamos supor que no servidor haja estes métodos:

servidor.geralancamentoscontabeis
---------------------------------------------
- abre a transacao.
- faz a apuração do que deve ser lancado.
- incrementa os lotes de lançamento.
- efetua os lançamentos.
- confirma a transacao

servidor.movimentacaodeestoquenf
---------------------------------------------
- abre a transacao.
- varre todos os itens da nf e faz a movimentacao (se nf de saida, reduz o saldo, senão, incrementa)
- manipula os lotes de produto
- confirma a transacao

servidor.geratitulosfinanceiro
------------------------------------
- abre a transacao.
- avalia notafiscal (entrada ou saída) e verifica a condição de pagamento utilizada
- gera o título e as parcelas no financeiro (se nf de saída, gera contas a receber, se não, contas a pagar)
- confirma a transacao

agora você precisa fazer, a partir do cliente, a chamada a dois métodos, nesta ordem:

servidor.movimentacaodeestoquenf
servidor.geratitulosfinanceiro

porém se o método servidor.geratitulosfinanceiro (o segundo) der algum problema, o primeiro método já estárá "comitado", certo? o que pode ocasionar um problema de consistência nos dados.

para que isso não aconteça, altere o servidor para:

servidor.geralancamentoscontabeis
---------------------------------------------
- verifica se há uma transação aberta. se não tiver, abre aqui.
- faz a apuração do que deve ser lancado.
- incrementa os lotes de lançamento.
- efetua os lançamentos.
- se a transação foi aberta aqui, confirma a transacao

servidor.movimentaestoquenf
---------------------------------------------
- verifica se há uma transação aberta. se não tiver, abre aqui.
- varre todos os itens da nf e faz a movimentacao (se nf de saida, reduz o saldo, senão, incrementa)
- manipula os lotes de produto
- se a transação foi aberta aqui, confirma a transacao

servidor.geratitulosfinanceiro
------------------------------------
- verifica se há uma transação aberta. se não tiver, abre aqui.
- avalia notafiscal (entrada ou saída) e verifica a condição de pagamento utilizada
- gera o título e as parcelas no financeiro (se nf de saída, gera contas a receber, se não, contas a pagar)
- se a transação foi aberta aqui, confirma a transacao

servidor.abretransacao
-----------------------------
- se houver uma transacao aberta, retorna falso, senão abre a transação e retorna verdadeiro

servidor.confirmatransacao
-----------------------------
- se houver uma transacao aberta, confirma a transacao (commit) e retorna verdadeiro, senão retorna falso

servidor.cancelatransacao
-----------------------------
- se houver uma transacao aberta, cancela a transacao (rollback) e retorna verdadeiro, senão retorna falso

note que todos os métodos estão no servidor.

agora, no cliente, você sabe que precisará chamar dois métodos do servidor, porém eles precisam trabalhar em conjunto. foi aí que eu sugeri:

try
  servidor.abretransacao; // transação no servidor, não no cliente
  cds1.applyupdates(-1);
  cds2.applyupdates(-1);

  servidor.movimentaestoquenf // método executado no servidor, porém não abrirá a transação
  servidor.geratitulosfinanceiro // método executado no servidor, porém não abrirá a transação

  servidor.confirmatransacao; // confirma a transação no servidor
except
  servidor.cancelatransacao; // cancela a transação no servidor
end;

não vejo outra forma de fazer algo desse tipo.

Responder

Gostei + 0

08/07/2011

Pedro Neto

Antes de iniciar, deixo aqui registrado o agradecimento ao Marco Salles pela solução. 
Em uma troca de e-mails com o Marco Salles (http://marcosalles.wordpress.com/), que também participa deste Fórum, o mesmo conseguiu através do exemplo que citei anteriormente no blog do Erick Sasse (http://www.ericksasse.com.br/applying-updates-to-more-than-one-clientdataset-in-a-single-transaction/) usando Remote Objects SDK, implementar um método no servidor que recebe o(s) Delta(s) do(s) ClientDataSet(s) das aplicações clientes e realiza o ApplyUpdates no lado servidor. Como falei, o objetivo é colocar as alterações dos ClientDataSets através de seus Deltas dentro uma transação no servidor. O método no servidor que utilizei para testar ficou assim:
 function TServerMethodsApp.DeltaApply(ADelta: OleVariant): Boolean;var  CdsList: TList<TClientDataSet>;  VCds: TClientDataSet;  i: Integer;  DeltaArray: array of OleVariant;begin  if VarIsArray(ADelta) then  begin    DeltaArray := ADelta;    CdsList := TList<TClientDataSet>.Create;    for i := Low(DeltaArray) to ( High(DeltaArray) div 2) do    begin      VCds := TClientDataSet.Create(self);      With VCds do      begin        ProviderName := DeltaArray[i + 1];        open;        data := DeltaArray[i];        Result := ApplyUpdates(0) = 0;      end;      CdsList.Add(VCds);    end;    for VCds In CdsList do      VCds.Free;    CdsList.Free;  end;end;



No lado do cliente, para testar, eu instancio um objeto da respectiva classe proxy gerada a partir do servidor que contém o método acima. Aí é só executar o método passando os parâmetros necessários em forma de um Array com os Deltas dos CDs e o nome dos respectivos DataSetProviders:
procedure TForm1.btn5Click(Sender: TObject);var  VServerRules: TServerMethodsAppClient;begin  VServerRules := TServerMethodsAppClient.Create(SQLConnection1.DBXConnection);  VServerRules.DeltaApply(VarArrayOf([cds1.Delta, cds1.ProviderName, cds2.Delta, cds2.ProviderName]));  FreeAndNil(VServerRules);end;


Por fim, basta implementar um método no servidor, que não será publicado no cliente, encapsulando o controle transacional (try finally), e possuindo um parâmetro do tipo método anônimo conforme exemplo abaixo. Aí fica fácil de encaixar as alterações dentro de um contexto transacional sem se preocupar com controle da transação. E ainda é possível combinar com regras implementadas no servidor de aplicação.



function TAppDataBaseService.ExecuteTransaction(ARoutine: TProc): Boolean;var  Vtran: TDBXTransaction;  AResult: Boolean;begin
  try    if not GetSessionConnection.Connected then      GetSessionConnection.Open;

    Vtran := GetSessionConnection.DBXConnection.BeginTransaction      (TDBXIsolations.ReadCommitted);
    ARoutine;    GetSessionConnection.DBXConnection.CommitFreeAndNil(Vtran);    AResult := True;  finally    GetSessionConnection.DBXConnection.RollbackIncompleteFreeAndNil(Vtran);    Result := AResult;  end;
end;


Responder

Gostei + 0

09/07/2011

Marco Salles

Beleza Pedro , sabe como é sabadão , frio ... coçando e acabei de desenvolver uma classe que gerencia
todos os AllApplayUpdates do client no Servidor .. Acredito que não so na tecnologia DataSnap , mas também em Desktop e com alguma adaptação funcionara tambpém em  client Servidor tradicional. Mas o proposito inicial da classe é o DataSnap

http://marcosalles.wordpress.com/2011/07/09/applayupdate-aplicacao-de-atualizacoes-para-mais-de-um-clientdataset-em-uma-unica-transacao-com-datasnap/

A vantagem da classe é o reaproveitamento bem como tirar a resposanbilidade de um form ou um dataModulo
ou mesmo de outra classe de ter que fazer isto . Além de facilitar a manutenção . A classe utiliza RTTI e não tem nenhum aclopamento com a classe Proxy gerada . Que dizer para vários servidores DataSnap desde que em uma questão contratual implemente a função AllApplayUpdates ( sobe o risco de se gerar uma exceção)


sabadão .. frio .. coçando...
Responder

Gostei + 0

09/05/2013

José

Este tópico esta sendo fechado por inatividade. Se necessário, sinalizar para que seja reaberto ou abrir um novo.
Responder

Gostei + 0

Utilizamos cookies para fornecer uma melhor experiência para nossos usuários, consulte nossa política de privacidade.

Aceitar