Quando o TClientDataSet e o TDataSetProvider são necessários?

Categoria: Artigos
Categoria Pai: Addicted 2 Delphi!
Acessos: 12397
Imagem meramente ilustrativa

Pra começar este artigo vou logo dizendo que não, nem sempre estes dois componentes precisam ser usados e o ganho ao usá-los pode não ser compensador pois seu uso pode aumentar a complexidade do seu código indiretamente. Se você está com dúvidas a respeito disso, recomendo fortemente esta leitura.

Antes de mais nada, entenda o que são estes dois componentes e evite gafes como programador, gafes estas que certamente vão introduzir complexidade desnecessária ao seu projeto:

O TClientDataSet implementa um DataSet independente de banco de dados, representando um conjunto de dados na memória. Um TClientDataSet pode ser usado como:

  1. Um conjunto de dados stand-alone totalmente funcional baseado em arquivo para aplicações de banco de dados de camada única (single-tier). Quando usado dessa maneira, o TClientDataSet representa os dados armazenados num arquivo dedicado no disco rígido do usuário

  2. Um buffer local (na memória) dos registros de um outro conjunto de dados (TQuery, TTable ou similares). Este conjunto de dados de origem pode residir no mesmo TForm ou TDataModule onde o TClientDataSet estiver. Esta forma de uso é obrigatória quando o DataSet de origem é unidirecional (os componentes DB Express são, por exemplo), sendo a única maneira de fornecer suporte de navegação e edição para seus dados. O conjunto de dados de origem também pode residir em um sistema separado quando o TClientDataSet é usado para implementar a parte do cliente de um banco de dados, numa aplicação de várias camadas (n-tier)

  3. Um armazenamento temporário (na memória) de dados em forma de conjunto de dados facilmente navegável e com recursos já prontos para inserção, exclusão, seleção e alteração de seus itens, tornando-se assim um meio prático para manipulação de qualquer tipo de informação, bem como exibição dessa informação em componentes conscientes de dados, o que permite a interação simplificada do usuário

 

O TDataSetProvider, como o nome sugere, fornece (provê) dados a partir de um conjunto de dados e resolve atualizações (inserções, exclusões e atualizações propriamente ditas) para esse conjunto de dados, o qual poderá aplicá-las ao SGBD associado a ele. De forma mais detalhada, o TDataSetProvider, empacota os dados de um conjunto de dados e passa este pacote em um ou mais DataPackets transportáveis para o TClientDataSet ou XML Broker[1]. O TClientDataSet, por sua vez, reconstrói os dados contidos no DataPacket a fim de criar uma cópia local (na memória), para o acesso simplificado do usuário. Quando o usuário termina de realizar suas ações nos dados (inserir, excluir, alterar), o TClientDataSet reempacota quaisquer dados alterados e envia estas alterações de volta para o TDataSetProvider, o qual aplica as atualizações no conjunto de dados original que por sua vez as replica no SGBD, terminando assim o ciclo "requisição-resposta". De forma resumida, um TDataSetProvider deve ser usado para:

Eu espero que você tenha lido com cuidado (e entendido) as definições acima. Elas foram criadas a partir da definição dos componentes na ajuda do próprio Delphi e também a partir de observações minhas e de outras pessoas mais experientes, as quais usam este par de componentes como se deve.


Interpretações errôneas sobre a função do TClientDataSet e do TDataSetProvider

De todas as coisas que eu já ouvi a respeito do uso desse par de componentes, a mais estúpida e absurda é a de que o simples fato de usar estes componentes já torna uma aplicação em uma aplicação n-tier. Sobre isso, eu lamento profundamente, mas se você acha isso eu creio que você não deve ter entendido bem o que é a divisão de uma aplicação em camadas e recomendo que você pare de ler este artigo e vá estudar sobre o assunto, tomando cuidado para não confundir camadas lógicas com camadas físicas. Neste artigo não está sendo aprofundado o assunto "multicamadas". Ele é apenas citado porque o TClientDataSet e o TDataSetProvider foram criados para permitir a criação de aplicações multicamadas com o Delphi (DataSnap), logo, é natural que alguns conceitos sejam citados.

Outra coisa falada de forma recorrente a respeito do uso destes componentes fora da arquitetura de n camadas é que usar essa dobradinha economiza recursos de rede e aumenta a performance geral de um programa implementado desta maneira. É justamente por conta desta premissa que eu resolvi escrever este artigo, pois muita gente está usando de forma errada os componentes e achando que eles estão cumprindo seu papel de "melhoradores de performance", mas na verdade só estão introduzindo complexidade desnecessária nos programas.

Como usar os componentes TClientDataSet e TDataSetProvider

A fim de tentar explicar os usos corretos desse par de componentes vou descrever abaixo as formas mais comuns de acesso a dados no Delphi:

  1. Seu programa acessa diretamente o SGBD utilizando componentes TQuery (ou similares), TUpdateSQL (ou similares) e TDatabase (ou similares). A figura abaixo mostra esta forma clássica de acesso a dados no Delphi, a qual é ensinada em praticamente todos os livros que tratam do assunto. Um iniciante certamente vai utilizar esta forma de acesso e a mesma pode ser usada em qualquer tipo de aplicação, pois é muito eficiente. Neste modelo o TQuery é automaticamente configurado para ser bidirecional (UniDirectional = False), o que permite sua ligação aos mais diversos controles conscientes de dados (DBAware) assim como permite a navegação para frente e para trás nos dados do TQuery, isto é, você pode usar Prior e Next. Aliás, vale salientar que você sempre só foi capaz de navegar no conjunto de dados porque a propriedade UniDirectional, por padrão, vem configurada como false.

  2. Seu programa acessa diretamente o SGBD utilizando componentes TQuery (ou similares), TUpdateSQL (ou similares), TDataSetProvider, TClientDataSet e TDatabase (ou similares). A figura abaixo mostra esta forma de implementação. Nesta implementação o TQuery no qual o TClientDataSet está ligado deve ser configurado como unidirecional, pois isso diminui o consumo de memória deste componente, que passa a utilizar internamente uma lista encadeada simples (onde cada registro conhece apenas o seu sucessor), ao invés de uma lista duplamente encadeada. Esta configuração pode diminuir até 50% do consumo de memória por registro no TQuery. Esta implementação também recomenda que a mecânica de funcionamento das telas seja baseada em múltiplas ações com apenas uma confirmação ao final. Em outras palavras, deve ser permitido ao usuário a realização várias ações (inserção, exclusão e alteração em qualquer ordem), as quais são mantidas em cache, para somente depois serem enviadas de uma só vez ao SGBD por meio de um ApplyUpdates. Outra vantagem desse modelo de conexão é que ele permite que seja implementado um "modelo de maleta" (Briefcase Model), onde um programa pode ficar totalmente desconectado de qualquer SGBD e apenas em momento oportuno a conexão seria feita e as alterações seriam efetivadas. Os componentes do DB Express (DBX) são unidirecionais, portanto esta forma de implementação é a única forma de trabalhar com eles e, neste caso, o Post pode ser seguido de ApplyUpdates caso você não queira implementar as ações em cache.

  3. Seu programa acessa indiretamente o SGBD, neste caso, deve existir uma aplicação no meio do caminho entre seus programas-cliente e o SGBD. Isso caracteriza uma aplicação em n camadas e você deve estar usando DataSnap, neste caso você certamente sabe o que faz e por conseguinte deve estar usando o TClientDataSet e o TDataSetProvider de forma correta. A figura abaixo mostra o uso dos componentes TClientDataSet e TDataSetProvider em uma aplicação DataSnap usando SOAP como transporte de dados (existem outros meios de transporte disponíveis no Delphi). Note que tais componentes ficam fisicamente separados (máquinas distintas), ligados apenas pela internet ou intranet. O DataSnap obriga você a usar TDataSetProvider e o TClientDataSet, porque estes componentes são os dois lados da mesma moeda (conexão). Do lado do servidor (middleware) fica o TDataSetProvider e do lado do cliente (thin client) ficam todos os TClientDataSet, cada um deles conectados aos seus TDataSetProviders correspondentes no servidor. É interessante perceber que o TDataSetProvider no servidor (middleware) atua como um TDataSource numa aplicação comum (cliente/servidor). De fato, se o TDataSetProvider fosse substituído por um TDataSource, o middleware seria idêntico a uma aplicação cliente/servidor. Eu costumo dizer que, do ponto de vista do SGBD, o middleware é um simples programa cliente/servidor. A fim de diminuir o consumo de memória no servidor, é recomendável que todos os TQuery sejam configurados como unidirecionais. No cliente, cada TClientDataSet é bidirecional SEMPRE e por isso é possível usá-los conectados a controles conscientes de dados sem qualquer problema

O foco deste artigo está nas linhas destacadas no item 2 acima. Vamos finalmente falar a respeito do mal uso dos componentes TClientDataSet e TDataSetProvider.

Como não usar o TClientDatSet e o TDataSetProvider

A desculpa inicial para o uso destes componentes é a performance, porque olhando a figura do item 2 se nota que, claramente, a utilização deles não torna a implementação mais simples, muito pelo contrário. Do ponto de vista da performance, existem dois pontos principais, os quais foram destacados de amarelo no item 2. O primeiro deles seria a suposta diminuição do consumo de memória local e o segundo diz respeito à diminuição do tráfego de rede por meio do agrupamento de ações e aplicação das mesmas em lote.

Se sua intenção é diminuir o consumo de memória então você não levou em conta algo muito simples: todo o ganho de memória obtido pelo uso de um cursor unidirecional no TQuery será compensado ou mesmo superado pela introdução do TDataSetProvider e do TClientDataSet, que é bidirecional tornando assim o consumo de memória SEMPRE MAIOR do que ao se usar a forma clássica de conexão (Item 1 acima). A recomendação é que o TQuery seja configurado como unidirecional não como uma vantagem opcional, mas sim como uma necessidade latente, já que a introdução do TClientDataSet duplicaria o consumo de memória e sendo assim, manter o TQuery como bidirecional nesta implementação é desperdício sério de memória e performance. Se nota que a configuração da propriedade UniDirectional no TQuery só faz mesmo sentido num middleware, pois lá não existe nada além de TQuery (ou similares). Considero, pois, que este mito do consumo de memória foi "detonado", como diriam os caras do Discovery Channel. Se sua única justificativa era essa, acho bom começar uma refatoração o mais rápido possível no seu código.

Sobre a diminuição do tráfego de rede, preciso explicar que não existe componente mágico pra fazer isso sozinho. Apenas o conjunto "implementação correta + componentes certos" pode fazer o tráfego de rede diminuir e neste caso, sim, é possível diminuir drasticamente o consumo de banda da aplicação, mas a utilização efetiva deste recurso requer que haja um botão adicional no seu TForm, que teria a função de enviar, por demanda, ao SGBD todas as alterações realizadas de uma só vez, ou seja, a persistência das alterações no SGBD deve ser feita em duas etapas para que essa economia de dados seja efetiva.

Se sua implementação usa recorrentemente Post + ApplyUpdates, e seus TDataSet não são unidirecionais por natureza (DBX), então eu lamento informar que você está subutilizando esta implementação e deveria repensar seriamente todo seu projeto. Em outras palavras, fazer Post + ApplyUpdates, transforma o conjunto TQuery + TDataSetProvider + TClientDataSet em uma redundância, pois o mesmo efeito pode ser conseguido com TQuery sozinho sem qualquer perda de performance. Se por acaso você estiver executando o ApplyUpdates TODA VEZ que uma operação for realizada, qual a vantagem de usar TDataSetProvider + TClientDataSet? Nenhuma! Pois seria o mesmo que realizar post ou delete diretamente no TQuery.

Conclusão

  1. Usar TDataSetProvider + TClientDataSet em uma aplicação cliente/servidor é OBRIGATÓRIO apenas quando se usa componentes cujos TDataSet são unidirecionais, tal como ocorre com os componentes do DB Express[2].

  2. Usar TDataSetProvider + TClientDataSet em uma aplicação cliente/servidor com o intuito de diminuir o consumo de memória local é um mito, e se você estiver usando a dobradinha apenas por conta disso, pare, pense e refaça seu projeto.

  3. Já do ponto de vista da economia de recursos de rede, usar TDataSetProvider + TClientDataSet em uma aplicação cliente/servidor só faz sentido se você pretende permitir ao usuário realizar várias operações e só no final confirmar todas ao mesmo tempo, ou seja, se você não está implementando em seu sistema uma forma de agrupar operações e fazer envio em lote das mesmas então o conjunto TDataSetProvider + TClientDataSet é desnecessário. Se você estiver fazendo Post + ApplyUpdates isso significa que você está subutilizando essa arquitetura e é desejável fazer tudo sem usar esses componentes, pois torna a codificação mais simples.


1 Nunca trabalhei com o XML Broker, mas pelo que pude entender lendo a ajuda do Delphi, ele é como um TClientDataSet, a diferença é que ao invés de converter os dados de um DataPacket, fornecido por um TDataSetProvider, em um conjunto de dados compatível com o TClientDataSet, ele converte esse DataPacket em XML, permitido uma utilização mais ampla de um servidor de aplicação desenvolvido em Delphi. Por exemplo, imagino que usando XML Brokers, seja possível converter os dados providos por um servidor de aplicação em XMLs que podem ser exibidos como páginas simples por um Browser (via XSLT) ou interpretados por outras linguagens, como PHP. Se você já trabalhou com o XML Broker, fique à vontade para comentar abaixo seu uso correto, ou simplesmente confirmar o que eu falei aqui ;)
2 Gostaria de agradecer aos amigos do grupo Delphi Experts que me alertaram sobre o fato de que os componentes do DB Express são unidirecionais e que apenas usando TDataSetProvider + TClientDataSet habilita seu uso com componentes DBAware