Zetta-Ømnis Soluções Tecnológicas
Desenvolvendo hoje a tecnologia do amanhã
Visite Nosso Patrocinador
Você está aqui:
DO NOT UNDERSTAND PORTUGUESE? CLICK ON THE FLAG TO CHANGE THE LANGUAGE!

Uso pleno do TClientDataSet e o Modelo de Maleta - O papel do TClientDataSet no DataSnap

Imagem meramente ilustrativa

O papel do TClientDataSet no DataSnap

A terceira solução, na qual usaremos a maior parte de nosso tempo aqui, posiciona o TClientDataSet como uma "maleta" do lado do cliente em uma aplicação DataSnap multicamadas. Isso significa que nós podemos desconectar a aplicação cliente e armazenar os dados localmente em arquivo, no formato binário MyBase. Nós podemos, sempre que quisermos, recarregar esses dados e continuar o trabalho localmente até quando uma conexão com o servidor DataSnap for restabelecida, permitindo que nós possamos enviar nossas alterações de volta ao servidor, mediante o uso do método ApplyUpdates.

Vale salientar que nós não focaremos nos detalhes do lado do servidor da arquitetura DataSnap, nem nos protocolos de comunicação utilizados nesta arquitetura. O foco será o TClientDataSet e suas habilidades de conexão com provedores locais ou remotos e a aplicação de atualizações ao middleware.

Por fim, a aplicação de atualizações (ApplyUpdates) pode resultar em erros de reconciliação (Reconcile Errors), os quais normalmente acontecem quando um ou mais usuários realizaram alterações conflitantes em um mesmo campo de um mesmo registro. Veremos então como podemos detectar e responder a estes erros usando a caixa de diálogo padrão para erros de reconciliação fornecida junto com o Delphi.


Arquitetura de banco de dados multicamadas

Usando uma arquitetura de banco de dados multicamadas você pode particionar uma aplicação de forma que ela possa acessar um banco de dados sem precisar de bibliotecas ou programas adicionais nas máquinas locais. Esta arquitetura também permite que você centralize as regras de negócio e processos, além de distribuir o processamento através da rede. O DataSnap suporta uma tecnologia de 3 camadas, a qual em sua forma clássica consiste de:

  • Um servidor de banco de dados em uma máquina (servidor)
  • Um servidor de aplicação em uma segunda máquina (camada do meio)
  • Um cliente magro (thin client) em uma terceira máquina (cliente)

O servidor de banco de dados pode ser qualquer um de sua preferência, InterBase, Postgres, Oracle, MySQL, SQL Server, dentre outros. O servidor de aplicação e o cliente magro podem ser desenvolvidos em Delphi, Kylix ou C++ Builder. É no servidor de aplicação onde se concentram as regras de negócio e todas as ferramentas necessárias para acessar e manipular os dados que estão no servidor de banco de dados. Os programas que serão utilizados pelos usuários não fazem nada além de mostrar-lhes os dados de forma que eles possam visualizá-los e editá-los.

O que é o DataSnap?

O DataSnap é baseado na tecnologia que permite que os conjuntos de dados sejam empacotados e enviados através da rede como parâmetros nas chamadas a métodos remotos (Remote Procedure Call - RPC). Ele inclui tecnologias que permitem converter um TDataSet em dados Variant ou XML do lado do servidor. No lado do cliente esses dados são transformados novamente em TDataSet[1] e exibidos ao usuário em um TDBGrid (por exemplo), com a ajuda dos componentes TClientDataSet ou TInetXPageProducer.

Olhando de um ângulo um pouco diferente, o DataSnap é uma tecnologia que permite mover um conjunto de dados (TTable, TQuery ou similares) de um servidor, para um TClientDataSet no cliente. O TClientDataSet, por sua vez, aparenta, age e se comporta exatamente como um TTable ou TQuery, exceto que ele não precisa estar ligado ao BDE ou outros componentes de conexão, como UniDAC ou FireDAC. O único requisito no cliente é a presença da biblioteca do DataSnap, a qual, aliás, se chama MIDAS.DLL. Mesmo assim, você pode dispensar a presença desta DLL, incluindo numa das cláusulas uses de sua aplicação cliente a unit MidasLib. Fazendo isso, nem mesmo a dll do DataSnap precisa estar presente! Ainda olhando para o DataSnap de uma forma diferenciada, basicamente o TClientDataSet obtém um conjunto de dados a partir de dados Variant que ele recebe do servidor.

O DataSnap permite que você utilize todos os componentes padrão do Delphi, incluindo componentes de acesso a dados do lado do servidor, mas o lado do cliente é um verdadeiro cliente magro, isto é, ele não deve incluir ou ligar-se diretamente a nenhum banco de dados. Como foi dito antes, a única dependência do cliente magro é a biblioteca midas.dll, a qual pode ser dispensada, mediante o uso da unit MidasLib.

Clientes DataSnap e Servidores DataSnap

Até agora, muita teoria, mas a melhor maneira de entender o que é o DataSnap e como ele funciona é construindo uma aplicação DataSnap, que consiste de um Cliente DataSnap e um Servidor DataSnap. Costumo começar com o servidor, onde encapsulo e exporto os conjuntos de dados. Com um servidor funcional, o próximo passo é construir o cliente que se conectará com este servidor e exibirá os dados de alguma forma.


O Sevidor DataSnap

Para construir o seu primeiro Servidor DataSnap (doravante chamado de SDS) você começa com uma aplicação nova vazia (File > New > Application[2]). Enquanto o formulário principal da aplicação estiver sendo exibido significa que o SDS estará ativo, em outras palavras, o loop de mensagens da aplicação vai manter o SDS vivo. Configure um título para o formulário principal de forma a identificar o SDS. Além disso eu costumo incluir neste formulário um TLabel com uma fonte grande e legível e configuro sua propriedade Caption com um valor sugestivo para o nome do SDS, por exemplo, "Meu primeiro servidor DataSnap".

Para transformar uma aplicação regular em um servidor de aplicação (middleware database server), você precisa adicionar um Remote DataModule (RDM) nela. Este DataModule especial pode ser encontrado na aba[3] "Multitier" do Object Repository do Delphi, logo, acesse File > New > Other e vá direto à aba Multitier, a qual mostra vários Wizards CORBA[4], um Remote DataModule e um Transitional DataModule[5]. O último pode ser usado com o MTS (Microsoft Transaction Server) antes do Windows 2000 ou COM+ no Windows 2000 ou posterior e não será coberto aqui. É o RDM "normal" que você precisa selecionar para criar seu primeiro SDS simples. Ao selecionar o ícone do RDM e clicar no botão OK, o assistente "New Remote DataModule Object"[6] será iniciado.

Existem algumas opções que você precisa especificar (ou garantir que estejam selecionadas corretamente). Primeiramente, "CoClass Name", que é o nome da classe interna. Este precisa ser um nome que você possa lembrar facilmente depois, então, eu recomendo que você use SimpleRemoteDataModule desta vez. Mantenha a opção "Instancing" configurada como Multiple Instance, desta forma seu SDS poderá conter múltiplas instâncias do RDM. Mantenha outras opções como estão e pressione OK para, finalmente, gerar o RDM.

O Remote DataModule[7]

O resultado da execução do assistente é um RDM que parece muito com um TDataModule regular. Visualmente não existem diferenças, e se você planeja usar o BDE, então você pode começar considerando-o como um TDataModule qualquer, soltando nele um componente TSession sem esquecer de configurar a propriedade AutoSessionName como true. Lembre-se de que fazer isso é imprescindível caso você esteja usando o modelo de thread Apartment[8].

Uma vez que você tenha incluído um componente TSession você poderá adicionar os outros componentes. Por exemplo, você pode incluir um TTable e nomeá-lo como TableCustomer. Configure sua propriedade DatabaseName como "DBDEMOS" e selecione na propriedade TableName a tabela customer.db.

Até agora tudo que foi feito poderia sê-lo em um DataModule regular, mas agora é hora de olhar os aspectos remotos deste DataModule especial (remoto). Na paleta de componentes, vá até a aba Data Access onde você encontrará o componente TDataSetProvider. Este componente é a chave para exportar conjuntos de dados de um RDM para o mundo exterior, mais especificamente para clientes DataSnap. Solte um componente TDataSetProvider no RDM e configure sua propriedade DataSet para TableCustomer. Isto significa que o TDataSetProvider irá prover ou exportar TableCustomer para um cliente DataSnap que se conectar nele (esse cliente será construído posteriormente neste artigo).

Uma propriedade muito importante do TDataSetProvider é a propriedade Exported, que é configurada como true para indicar que TableCustomer é exportada. Você pode configurar esta propriedade como false para "esconder" o fato de que TableCustomer é exportada a partir do RDM, assim, nenhum cliente poderá se conectar a ela. Isso pode ser útil, por exemplo em um servidor rodando constantemente (24x7), onde você precisa fazer o backup de algumas tabelas e precisa garantir que ninguém está trabalhando com elas durante o backup. Com a propriedade Exported do TDataSetProvider configurada como false ninguém pode fazer conexões a ele até que você configure novamente a propriedade como true.

Compilando o Servidor DataSnap

Basicamente isso é tudo que precisa ser feito para criar o seu primeiro SDS. A única coisa que resta ser feita é salvar o projeto. Salvei o formulário principal no arquivo "ServerMainForm.pas", o RDM foi salvo no arquivo "RDataMod.pas"[9], e salvei o projeto do SDS no arquivo "SimpleDataSnapServer.dpr". Após ter salvo o projeto, precisamos compilá-lo e executá-lo. Ao executar o SDS -- o qual mostrará apenas o formulário principal, claro -- ele será registrado[10] (no registro do Windows), assim qualquer cliente DataSnap poderá encontrá-lo e então conectar-se a ele. Mesmo que você mova o SDS para outro diretório (na mesma máquina), você precisará apenas executá-lo novamente para que ele seja re-registrado na nova localização. Esta é uma forma muito conveniente de gerenciar servidores DataSnap.

Até aqui, você não escreveu uma linha de código sequer para implementar este servidor DataSnap simples. Vamos ver então o que teremos de escrever ao implementar o cliente DataSnap que se conectará a ele.

O Cliente DataSnap

Existem vários tipos de clientes DataSnap que você pode desenvolver. Aplicações comuns de Windows (GUI), Active Forms e até mesmo servidores Web (usando Web Broker ou Internet Express). De fato, praticamente qualquer coisa pode atuar como um cliente DataSnap, como você verá em breve. Por enquanto você deve criar apenas uma aplicação regular de Windows a qual irá atuar como o seu primeiro cliente DataSnap simples que vai se conectar no seu SDS, o qual foi desenvolvido nas seções anteriores. Neste ponto você não deve tentar executar o cliente e o servidor em máquinas separadas. Ao invés disso, execute tudo em uma máquina e então, depois, você pode distribuir a aplicação na rede.

Acesse File > New > Application para inicializar a construção de uma aplicação simples. Neste ponto você decide se quer adicionar ou não um DataModule. A fim de evitar screenshots desnecessários neste artigo, eu não pretendo usar um DataModule. Ao invés disso, vou usar o formulário principal como contêiner para meus componentes não visuais (DataSnap), bem como para meus componentes visuais normais.

Antes de mais nada, seu cliente DataSnap precisa fazer uma conexão com o SDS. Esta conexão pode ser feita usando vários protocolos distintos, como (D)COM, TCP/IP (sockets) e HTTP e os componentes que implementam estes protocolos de conexão são, respectivamente, TDCOMConnection, TSocketConnection, TWebConnection e TCorbaConnection[11] na aba DataSnap e o TSoapConnection na aba Web Services. Para este primeiro cliente DataSnap simples usaremos o componente TDCOMConnection, logo, inclua este componente no seu formulário principal.

O componente TDCOMConnection tem uma propriedade chamada ServerName a qual contém o nome do SDS no qual você pretende conectar. Na verdade, se você abrir a lista disponível na propriedade ServerName no Object Inspector, você verá uma lista de todos os servidores DataSnap registrados na máquina local. No seu caso, esta lista deve conter apenas um item de nome SimpleDataSnapServer.SimpleRemoteDataModule, mas eventualmente todos os servidores MIDAS 3 e DataSnap que estiverem registrados serão vistos nesta lista. Os nomes nesta lista consistem de duas partes; a parte antes do ponto, denota o nome da aplicação e a parte após o ponto denota o nome do RDM, então, no caso atual, você vai selecionar o SimpleRemoteDataModule contido na aplicação SimpleDataSnapServer.

Quando você selecionar um servidor, a propriedade ServerGUID será automaticamente preenchida com o valor correto que está salvo no registro do Windows. Desenvolvedores com uma memória prodigiosa podem digitar um valor na propriedade ServerGUID e comprovar que a propriedade ServerName será preenchida como o nome do SDS correspondente. A diversão começa mesmo quando você configura a propriedade Connected do componente TDCOMConnection como true. Para que a conexão seja feita, o SDS precisa estar em execução, logo, o ato de configurar a propriedade Connected como true faz com que o SDS seja executado automaticamente, fato que pode ser comprovado pela exibição do formulário principal do SDS criado nas seções anteriores.

Client DataSets

Configure a propriedade Connected do componente TDCOMConnection como false, para fechar o SDS. Agora que você comprovou que é capaz de conectar-se a ele é hora de importar alguns DataSets que foram exportados pelo componente TDataSetProvider no RDM. Inclua um TClientDataSet, localizado na aba Data Access, no formulário principal e conecte a sua propriedade RemoteServer ao componente TDCOMConnection. Para que o TClientDataSet obtenha dados do SDS você precisa especificar qual TDataSetProvider usar. Em outras palavras, a partir de qual TDataSetProvider você deseja importar os dados que preencherão o TClientDataSet. Utilize a propriedade ProviderName para especificar o TDataSetProvider de sua escolha. Abra a lista do combobox associado a propriedade e você verá todos os TDataSetProvider disponíveis no RDM, todos aqueles que tem a propriedade Exported configurada como true. Em nosso caso existe apenas um TDataSetProvider no RDM do SDS, logo, selecione-o na lista.

Antes de escolher um valor para a propriedade ProviderName, lembre-se que você fechou a conexão com o SDS, entretanto, quando você abriu a lista da propriedade a fim de listar todos os componentes TDataSetProvider disponíveis no RDM que possuem sua propriedade Exported configurada como true, existe apenas uma forma (para o Delphi e o Object Inspector) de saber exatamente quais destes TDataSetProvider estão disponíveis: perguntar ao SDS! Mais especificamente, varrer o RDM em busca de componentes TDataSetProvider que possuem a propriedade Exported = true. Por conta desta necessidade, como o SDS estava desligado, ele precisa ser iniciado novamente de forma que a lista da propriedade ProviderName seja preenchida e exibida no Object Inspector. Como resultado disso, no momento em que você clica no combobox da propriedade ProviderName para exibir sua lista, o SDS será automaticamente iniciado novamente.

Quando você selecionar o RemoteServer e o ProviderName você poderá abrir (ou ativar) o TClientDataSet. Você pode fazer isso configurando a propriedade Active do componente como true. Neste momento o SDS estará alimentando dados a partir do TTable de nome TableCustomer, via componente TDataSetProvider através de uma conexão COM para o componente TDCOMConnection que roteia estes dados para o componente TClientDataSet no seu Cliente DataSnap, que estará pronto para ser usado.

Você pode agora soltar um componente TDataSource e acessar a aba Data Controls da paleta de componentes e incluir um ou mais controles conscientes de dados (data-aware). A fim de manter o exemplo simples, inclua apenas um componente TDBGrid. Conecte a propriedade DataSet do componente TDataSource ao TClientDataSet e conecte a propriedade DataSource do TDBGrid ao componente TDataSource. Como o componente TClientDataSet já estava ativado, você verá dados "ao vivo" em tempo de projeto, dados estes providos pelo nosso SDS.

Está tudo quase pronto agora. Para finalizar o Cliente DataSnap, primeiramente altere a propriedade Caption do formulário principal para algum nome útil, como por exemplo "Simple DataSnap Client" e salve seu trabalho. Nomeie o formulário principal como "ClientForm", salve-o como "ClientMainForm.pas", e nomeie o projeto como "SimpleDataSnapClient". Então, você estará pronto para compilar e executar o Simple DataSnap Client! Novamente você não escreveu nenhuma simples linha de código, mas esteja avisado que isso vai mudar em breve nas próximas seções.


BriefCase Model (Modelo de Maleta)

Ao executar o Cliente DataSnap você vê os dados de TableCustomer dentro do TDBGrid. Você pode navegar através destes dados, alterar o valor dos campos e mesmo incluir novos registros ou excluir registros existentes. Entretanto, uma vez que você fechar a aplicação, todas as mudanças serão perdidas. Não importa quantas vezes você tentar; as mudanças feitas nos dados do TDBGrid em tempo de execução do Cliente DataSnap afetam apenas o TClientDataSet local e não o TableCustomer no servidor.

O que você acaba de experimentar aqui é aquilo que é chamado de Modelo de Maleta (Briefcase Model). Usando o modelo de maleta (MDM) você é capaz de desconectar o cliente da rede e ainda conseguir acessar os dados. O MDM funciona da seguinte maneira:

  • Salve um conjunto de dados remoto no disco local, desligue a sua máquina e desconecte-a da rede. Você pode então religar a sua máquina e editar o seu conjunto de dados sem estar conectado à rede

  • Quando a rede estiver disponível novamente, você pode reconectar e atualizar o banco de dados. Um mecanismo especial está disponível para notificá-lo acerca de erros de banco de dados. Isso permite que o usuário possa resolver quaisquer conflitos que ocorram. Por exemplo, se duas pessoas editarem o mesmo registro, então uma delas será notificada do fato e serão apresentadas opções para a resolução do conflito

O ponto chave do MDM é que você não precisa ter o servidor disponível todo o tempo para poder trabalhar nos seus dados. Esta capacidade é perfeita para usuários com notebooks ou sites, onde você quer diminuir ao máximo o tráfego no banco de dados.

Você agora deve entender que seu Cliente DataSnap atua apenas nos dados locais que estão dentro do TClientDataSet e que é possível salvar estes dados em um arquivo local e carregá-lo novamente em momento oportuno, logo, indo ao que interessa, para salvar o conteúdo atual do TClientDataSet, inclua um TButton no formulário, nomei-o como "ButtonSave", configure seu Caption como "Salvar" e escreva o seguinte código no manipulador do seu evento OnClick:

procedure TClientForm.ButtonSaveClick(Sender: TObject);
begin
  if ClientDataSet1.ChangeCount > 0 then
    ClientDataSet1.SaveToFile('customer.cds')
end;

Este código salva todos os registros contidos no TClientDataSet em um arquivo de nome "customer.cds" no diretório atual da aplicação, mas apenas se houve alterações no TClientDataSet (ClientDataSet1.ChangeCount > 0). Aliás, "CDS" significa "Client DataSet", mas você pode usar qualquer nome de arquivo e qualquer extensão, claro. Observe que o segundo parâmetro do método SaveToFile é implicitamente dfBinary. Este valor indica que queremos salvar os dados no formato binário (proprietário da Borland). Alternativamente poderíamos utilizar no segundo parâmetro o valor dfXML para salvar os dados em formato XML. Um arquivo XML é muito maior (14K contra apenas 7K para todos os dados de TableCustomer), mas tem a vantagem de poder, em tese, ser usado por outras aplicações. Eu prefiro o formato binário, que gera um arquivo menor e mais eficiente.

De forma similar, para implementar a funcionalidade que permite carregar o arquivo customer.cds no TClientDataSet, inclua outro TButton no formulário principal, nomeie-o como "ButtonLoad", configure seu Caption como "Carregar" e escreva o seguinte código no manipulador do evento OnClick:

procedure TClientForm.ButtonLoadClick(Sender: TObject);
begin
  ClientDataSet1.LoadFromFile('customer.cds')
end;

Perceba que o método LoadFromFile não precisa de um segundo argumento; ele é suficientemente esperto para determinar se está lendo um arquivo binário ou um XML[12]

Armado com estes dois botões você poderá agora, localmente, salvar as alterações feitas em seus dados e recarregá-los posteriormente. A fim de controlar o fato de quando ou não o TClientDataSet estará conectado "ao vivo" ao SDS, você pode incluir mais um TButton no formulário principal que alterna a propriedade Active do TClientDataSet. Nomeie esse novo TButtom como "ButtonConnect", configure sua propriedade Caption como "Conectar", em seguida escreva o trecho de código a seguir no seu manipulador do evento OnClick:

procedure TClientForm.ButtonConnectClick(Sender: TObject);
begin
  if ClientDataSet1.Active then // Fecha e desconecta
  begin
    ClientDataSet1.Close;
    DCOMConnection1.Close;
  end
  else // Abre (vai conectar automaticamente)
  begin
//  DCOMConnection1.Open;
    ClientDataSet1.Open;
  end
end;

Perceba que para desconectar você precisa fechar o TClientDataSet e fechar também o TDCOMConnection, enquanto que para estabelecer a conexão você apenas precisa abrir o TClientDataSet que irá, implicitamente, abrir o TDCOMConnection também.

Finalmente, existe mais uma coisa que precisa ser feita: garantir que o TDCOMConnection e o TClientDataSet não estejam conectados ao SDS em tempo de projeto, do contrário, sempre que você reabrir o projeto do Cliente DataSnap no Delphi ele precisará realizar uma conexão com o SDS, que precisará ser carregado e se, por um motivo qualquer, o SDS não for encontrado em sua máquina o carregamento do projeto ficará congelado e, consequentemente, o Delphi ficará sem responder por um bom tempo. Para resolver esse problema, eu sempre garanto que estes componentes não estejam conectados em tempo de projeto. Para fazer isso você deve sempre atribuir false à propriedade Connected do componente TDCOMConnection (que vai fechar o formulário principal do SDS) e false à propriedade Active do componente TClientDataSet (que, como resultado, fará com que você não veja mais qualquer dado em tempo de projeto).

Gostaria de abrir um parêntese para discutir o processo de timing dos clientes quando eles não conseguem conversar com o servidor. Se você tentar conectar-se ao servidor DCOM, mas não puder alcança-lo, o sistema não desistirá imediatamente da busca, ao invés disso, ele vai continuar tentando por um período de tempo que raramente excederá dois minutos. Durante estes dois minutos, entretanto, a aplicação ficará ocupada e parecerá travada. Se esta aplicação estiver carregada na IDE, então o Delphi como um todo ficará travado. Você pode ter esse problema simplesmente ao configurar a propriedade Connected do componente TDCOMConnection como true.

Agora, quando você recompilar e executar seu Cliente DataSnap, o formulário principal será exibido sem nenhum dado sendo exibido no TDBGrid. É hora então de clicar no botão "Conectar", de forma que a conexão com o SDS seja estabelecida e todos os registros sejam obtidos a partir de TableCustomer. Entretanto haverá momentos em que você não terá acesso ao SDS, seja porque você está "na rua" ou simplesmente porque a máquina com o SDS encontra-se inacessível. Nestes casos você poderá clicar o botão "Carregar" e trabalhar com uma cópia local dos registros. Tenha em mente que esta cópia local é aquela que você salvou pela última vez e será atualizada apenas quando você clicar o botão "Salvar" para escrever todo o conteúdo do TClientDataSet no disco.


ApplyUpdates

É excelente ser capaz de conectar-se a um conjunto de dados remoto ou carregar um conjunto de dados local e depois salvá-lo no disco de novo, mas como se aplicam as atualizações[13] ao banco de dados de fato (remoto)? Isso pode ser feito usando-se o método ApplyUpdates do TClientDataSet. Inclua mais um TButton no formulário principal, configure seu nome como "ButtonApplyUpdates" e seu Caption como "Aplicar Alterações". Tal como o botão "Salvar", este botão deve apenas aplicar alterações caso haja alterações a serem aplicadas. O manipulador do evento OnClick deste botão segue:

procedure TClientForm.ButtonApplyUpdatesClick(Sender: TObject);
begin
  if ClientDataSet1.ChangeCount > 0 then
    ClientDataSet1.ApplyUpdates(0);
end;

O método ApplyUpdates tem apenas um argumento: o número máximo de erros que serão permitidos antes que o processo de aplicação de alterações seja interrompido (MaxErrors). Com apenas um Cliente DataSnap conectado ao SDS você nunca terá qualquer problema, então, fique à vontade para executar o Cliente DataSnap agora. Clique o botão "Conectar" para conectar (e carregar) o SDS e use os botões "Salvar" e "Carregar" para escrever /ler o conteúdo do TClientDataSet no/do disco. Você pode até remover sua máquina da rede e trabalhar apenas nos dados locais por uma quantidade significante de tempo, que é exatamente a ideia por trás do modelo de maleta (o seu laptop sendo a maleta!). Qualquer alteração feita na sua cópia local permanecerá visível e você poderá aplicar as alterações no banco de dados remoto com um clique no botão "Aplicar Alterações", claro, desde que você reconecte à rede e ao SDS novamente.

Manipulação de erros

Então o que aconteceria se dois clientes, ambos usando o MDM, conectassem ao SDS, obtivessem todo conteúdo de TableCustomer e ambos fizessem algumas alterações no primeiro registro? De acordo com o que foi codificado até o momento, ambos os clientes poderiam enviar suas alterações ao SDS usando o método ApplyUpdates. Se ambos passaram zero como argumento do ApplyUpdates, então o cliente que ficou em segundo lugar na "corrida da atualização" não terá suas modificações persistidas no banco de dados remoto, pois seu ApplyUpdates vai ser interrompido imediatamente. O segundo cliente poderia utilizar um valor maior que zero em MaxErrors a fim de indicar um número fixo de erros/conflitos que seriam permitidos antes de o ApplyUpdates ser interrompido, entretanto, mesmo se o segundo parâmetro fosse -1 (o que indicaria que o ApplyUpdates deve continuar mesmo que haja erros), ele jamais iria alterar os dados que foram previamente alterados pelo primeiro cliente. Em outras palavras: você precisa executar algumas ações de conciliação para manipular atualizações em registros e campos que foram alterados por outros usuários.

Felizmente o Delphi possui uma caixa de diálogo muito útil especialmente desenvolvida para este propósito. Sempre que você precisar realizar alguma conciliação de erros[14], você deve considerar a adição desta caixa de diálogo ao seu Cliente DataSnap (ou escrever uma você mesmo, mas algo sempre precisa ser feito a respeito destes erros). Para usar a caixa de diálogo disponibilizada pelo Delphi, vá em File > New > Other, acesse a aba (ou item) "Dialogs"[15] do Object Repository e selecione o ícone "Reconcile Error Dialog". Uma vez que você tenha selecionado este ícone e clicado OK, uma nova unit será adicionada ao projeto do Cliente DataSnap. Esta unit contém a definição e a implementação da caixa de diálogo "Update Error", que pode ser usada para resolver erros decorrentes de conflitos nas operações de banco de dados.

Quando esta unit for adicionada ao projeto existe uma coisa muito importante que você precisa verificar. Primeiramente salve seu trabalho. Salve a nova unit em um arquivo de nome ErrorDialog.pas. Feito isso, a menos que você tenha desmarcado a opção para criar automaticamente TForms e TDataModules (na aba "Designer" da caixa de diálogo Tools > Environment Options), você precisará garantir que a classe da caixa de diálogo "Update Error" (TReconcileErrorForm) não seja uma das que são automaticamente criadas por sua aplicação. Veja a aba "Forms" da caixa de diálogo Project > Options. Nesta aba, você encontrará uma lista de formulários que são automaticamente criados e uma lista de formulários que estão disponíveis no projeto. Apenas verifique se TReconcileErrorForm não está na lista de formulários que são criados automaticamente e, se estiver, mova-a para a lista de formulários disponíveis. Uma instância de TReconcileErrorForm será criada dinamicamente quando for necessária.

Então, quando ou como você pode usar esta caixa de diálogo especial? Bem, na verdade é muito simples. Para cada registro cuja operação  de banco de dados (inserção, exclusão, alteração) não seja bem sucedida, o evento OnReconcileError do TClientDataSet será chamado. O manipulador deste evento tem a seguinte assinatura:

procedure TClientForm.ClientDataSet1ReconcileError(DataSet: TCustomClientDataSet;
  E: EReconcileError; UpdateKind: TUpdateKind; var Action: TReconcileAction);
begin

end;

Este é um manipulador de evento com quatro argumentos. O primeiro deles é o TClientDataSet que levantou o erro. O segundo é a exceção de (re)conciliação propriamente dita, a qual contém a mensagem acerca da causa da condição de erro. O terceiro argumento é o UpdateKind (ukInsert, ukDelete, ukModify) e indica qual operação estava sendo tentada quando o erro aconteceu. Finalmente, o quarto argumento é a ação que deve ser tomada para resolver o conflito. Neste argumento você pode retornar os seguintes valores da enumeração TReconcileAction, declarada na unit DBClient (ou DataSnap.DBClient):

  • raSkip - Não altere nada no banco de dados mas deixe as alterações não aplicadas no log de modificações (localmente), para permitir uma tentativa posterior;
  • raAbort - Abortar toda a manipulação de erros. Nenhum outro registro vai passar pelo evento OnReconcileError;
  • raMerge - Mescla os dados existentes no registro salvo (banco de dados remoto) com as alterações sendo tentadas. Isso vai alterar remotamente APENAS aqueles campos que foram alterados localmente;
  • raCorrect - Substitui dados do registro salvo (banco de dados remoto), com valores corrigidos informados. Esta é a opção na qual a intervenção do usuário é requerida;
  • raCancel - Desfaz todas as alterações no registro atual, transformando-o de volta no registro original (local) que você tinha;
  • raRefresh - Desfaz todas as alterações no registro atual, mas recarrega o registro com os valores contidos no banco de dados (remoto).

A coisa mais legal a respeito do TReconcileErrorForm é que você não precisa se preocupar com nada disso. Você precisa apenas fazer duas coisas. Primeiro, você precisa incluir a unit ErrorDialog na cláusula uses do formulário principal. Com o formulário principal aberto no Delphi, pressione a combinação de teclas Alt+F11. A caixa de diálogo "Use unit" vai aparecer. Selecione a unit ErrorDialog e clique OK.

A segunda coisa a fazer é escrever uma linha de código no manipulador do evento OnReconcileError e chamar a função HandleReconcileError, disponível na unit ErrorDialog, a qual você acabou de incluir na cláusula uses de seu formulário principal. A função HandleReconcileError tem os mesmos quatro argumentos do manipulador do evento OnReconcileError (não é coincidência, claro), então, tudo agora é questão de passar estes argumentos do manipulador para a função, nada mais, nada menos. Sendo assim, o manipulador completo do evento OnReconcileError pode ser visto abaixo:

procedure TClientForm.ClientDataSet1ReconcileError(DataSet: TCustomClientDataSet;
  E: EReconcileError; UpdateKind: TUpdateKind; var Action: TReconcileAction);
begin
  Action := HandleReconcileError(DataSet,UpdateKind,E);
end;

Demonstrando Erros de (Re)Conciliação

A maior questão agora é: como tudo isso funciona na prática? Para poder testar, você obviamente precisa de dois ou mais Clientes DataSnap rodando simultaneamente. Para um teste completo usando o Cliente DataSnap e o Servidor DataSnap, você precisa executar os seguintes passos:

  • Inicie o primeiro Cliente DataSnap e clique o botão "Conectar". Neste momento o SDS será carregado também e dados serão obtidos;
  • Inicie o segundo Cliente DataSnap e clique o botão "Conectar". Dados serão obtidos do mesmo SDS que já está em execução;
  • Usando o primeiro Cliente DataSnap, altere o campo "Company" do primeiro registro;
  • Usando o segundo Cliente DataSnap, altere o mesmo campo "Company" do primeiro registro, tomando o cuidado de não alterar ele para o mesmo valor informado no primeiro Cliente DataSnap;
  • Clique o botão "Aplicar Alterações" do primeiro Cliente DataSnap. A alteração será aplicada sem qualquer problema;
  • Clique o botão "Aplicar Alterações" do segundo Cliente DataSnap. Desta vez, um ou mais erros vão ocorrer porque o primeiro registro teve o campo "Company" alterado (pelo primeiro Cliente DataSnap). Para esse e para outros registros conflitantes o evento OnReconcileError será executado e a caixa de diálogo "Update Error" será apresentada;
  • Dentro da caixa de diálogo "Update Error" você poderá experimentar as várias ações de (re)conciliação (Skip, Abort, Merge, CorrectCancel e Refresh) a fim de obter um bom entendimento do que cada uma delas faz. Preste atenção redobrada nas diferenças entre Skip e Cancel, e as diferenças entre Correct, Refresh e Merge.

"Skip" vai ignorar o erro atual sem aplicar nada ao banco de dados remoto e seguir para o próximo registro da fila de atualizações no Data Packet (se houver). A alteração não aplicada no banco de dados remoto permanecerá no log de modificações e poderá ser enviada novamente ao se pressionar o botão "Aplicar Alterações". "Cancel" também vai ignorar o erro atual e mantê-lo no log de modificações, a diferença é que ele vai interromper o processo de aplicação de alterações, ou seja, se houver mais registros a serem atualizados, eles não o serão!

Para deixar isso bem claro, imagine que existem 10 registros modificados e você pressiona o botão "Aplicar Alterações". O primeiro, o segundo e o terceiro registros, passaram. O quarto registro tem um problema e vai exibir a caixa de diálogo "Update Error". Se você escolher "Skip", a atualização do quarto registro será ignorada, ele permanecerá no log de modificações locais do TClientDataSet e o fluxo do programa continua tentando aplicar o quinto registro. Supondo que o quinto registro passe, o sistema vai tentar o sexto e depois o sétimo. Suponha que esses passaram sem problemas, aí, ao tentar aplicar as atualizações do oitavo registro, houve um erro, então, novamente, a caixa de diálogo "Update Error" vai aparecer, mas desta vez a ação que você escolhe é "Cancel". Neste caso, o restante da operação do ApplyUpdates terminará e os registros oitavo, nono e décimo, não serão aplicados e permanecerão no log de alterações locais.

"Refresh" apenas "esquece" todas as atualizações que você fez no registro e atualiza ele localmente com os valores que estão persistidos no servidor de banco de dados. "Merge" vai tentar mesclar o registro modificado (local) com o registro salvo (remoto), colocando suas alterações no registro que está no servidor de banco de dados. "Refresh" e "Merge" resolvem o problema imediatamente, isto é, após a aplicação destas ações, os registros aos quais estas ações foram aplicadas são removidos do log de modificações e será garantido que tanto o registro remoto como o registro local tenham exatamente os mesmos valores em seus campos. Isso não acontece ao se usar as ações "Skip" ou "Cancel", as quais mantém os registros no log de alterações para permitir um reenvio das alterações por meio de novo ApplyUpdates.

"Correct" é sem dúvida a opção mais avançada. Ela dá ao usuário a opção de informar na própria tela "Update Error" (ou via código) os valores corretos não conflitantes de cada um dos campos do registro. Tal como as opções "Refresh" e "Merge", após a aplicação desta ação os registros aos quais esta ação for aplicada serão removidos do log de modificações e será garantido que tanto o registro remoto como o registro local tenham exatamente os mesmos valores em seus campos.

Sumário

Neste artigo eu descrevi como o TClientDataSet pode ser usado como uma tabela stand-alone na memória, assim como seu uso em conjunção com o dbExpress (em duas camadas) e com o DataSnap (em três camadas) como cache de banco de dados, provendo o assim chamado Modelo de Maleta. Com o engessado (e congelado) BDE, a importância do TClientDataSet continua a crescer nas aplicações desenvolvidas em Delphi, Kylix e no C++ Builder!

Para mais informações leia Dr. Bob Eaxamines #63 em "Tecnologias de acesso a dados no C++ Builder" e #58 "Desenvolvimento de Aplicações de Bancos de Dados", bem como meus artigos[16] do BorCon 2004 em "Técnicas de acesso a dados com ClientDataSets" e do BorCon 2003 em "Indrodução ao ClientDataSet e ao dbExpress".



1 Costuma-se chamar estas conversões de objetos em bytes portáveis e vice-versa de serialização de objetos
2 Esse caminho muda em alguns Delphis mais recentes. Em Delphi 2006, por exemplo, o caminho é File > New > VCL Forms Application
3 Em versões mais modernas do Delphi, o Object Repository é composto de uma visualização estilo "Windows Explorer" com itens sendo exibidos em pastas, logo, nestas versões existe uma pasta chamada Multitier
4 Este item pode não existir na sua versão de Delphi
5 Este item pode não existir na sua versão de Delphi
6 Este nome pode variar
7 Esta seção está focada no BDE, que era a tecnologia nativa de conexão disponível no Delphi na época em que este artigo foi escrito. Obviamente você não precisa (e nem deve) usar mais o BDE. Resumidamente, ignore as referências ao BDE e inclua no RDM componentes correlatos disponíveis no suíte de componentes de conexão de sua preferência (FireDAC, UniDAC, ZeosLib, DBX, etc.)
8 Ao criar o RDM, o modelo de thread padrão é o Apartment, logo, você precisará configurar a propriedade AutoSessionName como true neste exemplo, caso esteja usando o BDE
9 Um arquivo adicional de extensão .tlb pode ter sido criado pelo assistente "New Remote DataModule Object". Caso haja necessidade de salvar este arquivo, nomeie-o com o mesmo nome do RDM, ou seja, RDataMod.tlb.
10 A partir do Delphi 2007 o registro automático foi descontinuado devido a presença maciça do UAC nos Windows, por isso, se você tentar rodar este programa ele não será registrado automaticamente. Se este for seu caso, basta executá-lo com a linha de comando /RegServer. Para desregistrá-lo, use a linha de comando /UnRegServer. O registro e o desregistro precisam ser feitos executando o SDS como administrador
11 Este componente pode não existir na sua versão de Delphi
12 O arquivo XML que pode ser carregado pelo TClientDataSet tem uma estrutura específca, no entanto, qualquer XML pode ser carregado por um TClientDataSet após sofrer uma transformação XML, ou seja, uma conversão que transforma uma estrutura XML qualquer numa estrutura XML que o TClientDataSet é capaz de entender (DATAPACKET). Para realizar a conversão, utilize o XML Mapper, disponível no diretório bin do Delphi. O XML Mapper vai gerar um arquivo de transformação XML (XSLT) com o qual, em tempo de execução, um arquivo XML "padrão" pode ser convertido em um XML DataPacket
13 Quero ressaltar novamente que a palavra "atualizar" aqui significa qualquer operação que altere o banco de dados. No caso, uma inserção, uma exclusão e uma alteração propriamente dita, todas estas operações alteram o banco de dados, inserindo, excluindo ou alterando registros
14 "Conciliar erros" ou "Reconciliar erros" vem do inglês "Reconcile Error" e pode ser interpretado como uma forma de resolver problemas (erros) nos dados quando o DataSnap não consegue determinar o que deve ser feito. A responsabilidade da decisão do que deve ser feito com dados conflitantes ou errados é sempre passada à aplicação ou ao usuário, o qual tem o poder de decidir o que deve ser feito
15 Nos Delphis mais recentes, o ítem do Object Repository é "Delphi Files" e o nome do ícone a ser selecionado é "VCL Reconcile Error Dialog"
16 Os links para estes artigos não existem mais. Mantive-os aqui apenas para registro histórico. Caso eles lhe interessem, contate Bob Swart diretamente para pedir uma cópia, caso ele ainda as possua.
Acesso Rápido
Não digite mais que o necessário...



Escaneie este QRCode em dispositivos móveis para acessar a página atual rapidamente nestes dispositivos
Conteúdo Verificado!
#BULLSHITFREE #CLICKBAITFREE
#MONEYLESS
Este site é amigo do desenvolvedor do mundo real
Gostou do conteúdo?
Se você gostou do conteúdo que está lendo, você pode ajudar a manter este site no ar doando qualquer quantia. Use os botões abaixo para realizar sua doação.
 
É rápido, fácil e indolor :)
 

Estatísticas em tempo real

Visite Nosso Patrocinador