Visual Basic, VB .NET, ASP, Active X, Access, SQL Server

Histórico de Objeto Composto

Criar históricos para os dados de uma aplicação é o mesmo que dar à aplicação uma dimensão nova: a dimensão do tempo. Informações de histórico não servem apenas para dirimir dúvidas sobre quem foi o responsável por este ou aquele erro, mas também é uma forma de acumular informações que poderão ser usadas para desenvolver aplicações que necessitem usar dados relativos a certos períodos ou  para melhor compreender o dia-a-dia das atividades da empresa. O trato das informações de histórico abre todo um conjunto novo de possibilidades para o planejamento baseado na experiência acumulada com o registro do dia-a-dia das atividades da empresa.

Porém ... , gravar históricos não é tão simples como pode parecer à primeira vista. Não basta criar uma tabela de históricos para cada tabela do sistema e registrar nestas tabelas tudo que ocorre em cada operação no banco de dados. As complicações vêm do fato de que as tabelas são feitas para existirem em relação umas com as demais. Quase sempre temos que relacionar as informações de uma tabela com as de outras para que seus dados façam sentido. E estes relacionamentos aparecem bem no modo como os programas têm de apresentar dados para os usuários. Ninguém está interessado em ver uma lista de pedidos sem que possa ver os itens dos pedidos, ou cadastrar um produto sem poder escolher a unidade de uma lista de unidades. O programador tem bastante trabalho para construir interfaces que ofereçam ao cliente a comodidade de ter tudo ao alcance de um click de mouse. A exibição de informações no estado presente é facilitado pelo fato de que o presente é único e compartilhado pelo último estado de todas as informações. O presente funciona como uma linha perpendicular à linha de progressão do tempo. Sobre a linha do presente temos o último estado de tudo que existe no momento atual. Basta percorrer esta linha para examinarmos o estado da totalidade das coisas. A exibição de informações do passado, no entanto, exige que lidemos com o problema de ter que descobrir que informações eram contemporâneas de quais quando algum evento ocorreu. As coisas se complicam quando queremos saber não apenas o estado de um objeto em determinado momento, mas também como estavam e quais eram os demais objetos relacionados a ele naquele momento. O que nos falta nestes casos é a linha do presente tal como ela era no momento passado. Para exibir coerentemente um momento do passado é preciso reconstruir a linha do presente tal como ela era na ocasião. Não nos basta ter separadamente a história dos estados de cada objeto de uma totalidade, é preciso saber que estados compartilhavam a mesma linha do presente a cada  momento. Tabelas de históricos sempre contêm a data em que os históricos foram gerados, mas a data não é uma forma confiável para armazenar a ordem em que os eventos ocorrem num sistema de informações. Tanto pela sua resolução, que é baixa, como pela estabilidade, já que pode ser alterada pelo usuário ou pelo mau funcionamento do computador. Mas há também os campos identidade que podem ser de auto-numeração crescente. Estes sim servem para exibir a ordem real dos estados salvos na tabela de históricos. O problema porém é que esta ordem não está relacionada com a ordem dos históricos das demais tabelas. Por exemplo, não há nada que me informe com exatidão qual era o último histórico do cliente "A" quando o produto "B" sofreu alteração de preço. Não tenho nem como saber se o cliente já havia sido cadastrado.
Para resolver o problema exposto acima, apresento a seguir uma forma de armazenamento dos relacionamentos existentes entre as tabelas de históricos para que se possa ter um meio de reconstruir a linha do presente para qualquer que seja o momento examinado na história de um objeto. Usando esta técnica é sempre possível levantar, para qualquer momento, quais eram os objetos existentes , quais eram seus relacionamentos e suas propriedades. Devo adiantar que a técnica de armazenamento é bastante simples, mas a recuperação dos dados é suficientemente complexa e trabalhosa para justificar a criação de uma ferramenta que automatize a tarefa. É nisto que estou trabalhando atualmente. Mas vamos à técnica de armazenamento.

Tabela:
RelacionamentosDeHistoricos
Layout:
StampId
StampDateTime
StampUser
StampAction
StampObjectType
StampObjectId
LastStampIdA
LastStampIdB
.
.
.
LastStampIdN

Na tabela RelacionamentosDeHistoricos temos um campo StampId, para servir de identificador único para cada registro da tabela; StampDateTime para conter a data e hora em que o registro foi criado; StampUser, para conter a identificação do usuário gerador da inclusão; StampAction, para conter o tipo de operação que causou a inclusão do registro; StampObjectType, para conter um valor convencionado na aplicação que identifique o tipo de objeto gerador do último histórico; StampObjectId, para conter o identificador único do objeto afetado pela operação e que é colhido da tabela em que os dados do objeto são armazenados. Os campos de nome prefixado por "LastStampId" são usados para conter o último StampId (identificador único) de cada tabela de históricos de objetos da composição. Para cada novo registro de histórico adicionado em uma destas tabelas, será também adicionado um registro na tabela RelacionamentosDeHistoricos. Quando da adição de um registro de RelacionamentosDeHistoricos, todos os valores para campos do tipo LastStampId serão mantidos iguais ao que eram quando da inclusão do último registro nesta tabela, exceto o campo LastStampId reservado para os identificadores únicos da tabela de históricos recém atualizada. Neste campo será armazenado o StampId do último registro incluído nesta tabela de históricos. No campo StampObjectType, será armazenado o tipo de objeto que teve sua tabela de históricos atualizada para gerar o registro de RelacionamentosDeHistoricos. Este valor é uma constante única que o programador cria para identificar cada tipo de objeto da composição. No campo StampObjectId, será armazenado o identificador do objeto afetado pela operação geradora do histórico. Estas últimas informações serão úteis na hora de recuperar os históricos da composição. Finalmente, no campo StampAction, será armazenada a ação que causou o histórico. Novamente teremos aí um valor correspondente a uma constante que o programador arbitrariamente cria para identificar cada tipo de ação: inclusão , alteração, exclusão, restauração.

Com as informações armazenadas na tabela RelacionamentosDeHistoricos, teremos o emparelhamento entre diferentes tabelas de históricos e também saberemos a ordem absoluta de criação dos históricos nestas tabelas. O campo StampId de RelacionamentosDeHistoricos nos dá esta ordem absoluta. Supondo que selecionemos um registro qualquer em uma tabela de históricos de algum tipo de objeto da composição. Chamemos o objeto gerador deste histórico de "A". Para sabermos como estavam todas as demais tabelas de históricos de objetos relacionados a "A" no momento em que o registro foi gerado, pesquisamos, na tabela RelacionamentosDeHistoricos, o primeiro registro que contém o StampId do registro de histórico de "A" considerado. Teremos então, no mesmo registro de relacionamento, o último StampId de cada uma das demais tabelas de histórico no momento da criação daquele histórico. Com os valores de StampId de cada tabela de histórico, poderemos buscar, nestas tabelas relacionadas, o último histórico de cada objeto que participava da composição com o objeto "A" naquele momento. Vale lembrar que o objeto "A" pode ter qualquer relação de parentesco com os demais, ou seja, o estado total da composição pode sempre ser obtido para qualquer momento da história de qualquer dos seus componentes. Veja na ilustração abaixo como as tabelas se relacionam pelos registros de RelacionamentosDeHistoricos.





A tabela RelacionamentosDeHistoricos contém os dados que servem como ponte para os dados dos objetos nas suas respectivas tabelas de históricos. O modo como pode ser feita a recuperação destes dados no caso de se querer reconstruir os objetos de uma composição em um determinado momento da sua história pode ser um entre muitos. A forma de recuperar os dados vai depender das relações entre os objetos na composição. Uma coisa é certa: será sempre necessário dividir a tarefa em algumas etapas. Para quem usa SQL Server, será necessário criar stored procedures com alguns cruzamentos de resultados de consultas; para quem usa Access, será necessário criar algumas consultas parametrizadas que serão chamadas umas de dentro de outras. Como o trabalho para quem usa base de dados Access será sempre um pouco mais difícil, procurarei dar os exemplos para Access. Estes exemplos são bastante complexos e já os tenho prontos, mas ainda não os testei adequadamente e ficarão para a continuação deste artigo em breve.