Friday, November 21, 2008

Cache de DataSets em aplicações multi-camadas III

OK, você tem um mecanismo que faz o cache de seus dados localmente e não precisa trafegar os dados a todo momento, certo? Mas como descobre se o seu cache reflete fielmente os dados no banco de dados?
Bem, existem bancos de dados que permitem notificação de aplicações clientes. Mas meu objetivo é criar algo genérico que não esteja atrelado a uma implementação específica de SGBD, ou seja, não será possível usar mecanismos de notificação.
Então o que resta é fazer a consulta diretamente. Eu sempre usei, em todas as tabelas, um campo que me informa a data/hora de inclusão/alteração. Vamos supor então que este campo se chama dat_hor_edi, ou seja, "data/hora de edição". Sempre que eu incluo um novo registro na tabela eu atualizo este campo com a data/hora atual. Procedo da mesma forma quando eu altero o conteúdo do registro. Então uma consulta como

SELECT MAX(dat_hor_edi) FROM minha_tabela


sempre me fornecerá a data/hora da última inclusão e/ou edição que ocorreu nesta tabela. Assim, se eu executei a minha consulta na hora "H" e a consulta acima me retornou um valor para "dat_hor_edi" MAIOR que "H", eu sei que a tabela sofreu inclusão e/ou edição, e meu cache não reflete mais a situação atual do banco de dados, e então o cache precisa ser atualizado.
Mas aí tem um problema: Esta consulta não me garante as exclusões. Claro, se eu excluir um registro desta tabela a consulta acima não me mostrará isto.
Então precisamos de um outro dado para garantir a integridade do cache. A data de última edição/inclusão funcionará perfeitamente junto com o número de registros da consulta. Estes dois números juntos me permitem saber com certeza se meu cache tem ou não uma cópia fiel da tabela no banco de dados. Eu precisaria de uma consulta do tipo:


SELECT
COUNT(*) as TblRecCount,
MAX(dat_hor_edi) as TblTimeStamp
FROM minha_tabela


O número de registros de uma consulta é prontamente obtido, mas o campo "dat_hor_edi" precisa ser atualizado de uma das duas formas:
1) Pela aplicação, sempre que houver inclusão e ou edição do registro;
2) Por um trigger no banco de dados, criado específicamente para isto;

Eu já usei e continuo usando ambas as formas. A primeira pode parecer mais complicada de se conseguir, mas não o é de fato. Basta que sua aplicação seja construída de forma metódica. Obviamente a primeira forma não prevê "marretas" feitas diretamente no banco de dados.

Então, o cache em si é composto por 3 diferentes "dados": O DataSet, o número de registros deste DataSet, e a data/hora de última inclusão/edição neste conjunto de dados. Está aí uma classe desenhada, que num pseudo-código pascal seria:


TDataSetCache = class
public
TimeStamp: TDateTime;
RecordCount: Integer;
DataSet: TDataSet;
end;


Algumas pessoas já me perguntaram: "mas então para eu saber se preciso fazer uma consulta, eu precisarei fazer antes outra consulta?". A resposta é: Sim, precisará!
Mas você em geral troca uma consulta que retorna, digamos, 1000 registros e que leva 10 segundos e uma enormidade de processamento do seu SGBD por uma outra consulta que trafega meia dúzia de bytes e leva milésimos de segundos para ser executada.
O primeiro objetivo do cache é diminuir o tráfego de dados!

Na próxima vamos implementar a classe acima, o TDataSetCache! :)

No comments: