Maio de 2016

Volume 31 - Número 5

Cutting Edge - Como criar um CRUD Histórico

Por Dino Esposito | Maio de 2016

Dino EspositoBancos de dados relacionais existem desde a década de 1970 e algumas gerações de desenvolvedores começaram e encerraram suas carreiras sem aprender, ou considerando apenas superficialmente, uma abordagem alternativa de armazenamento de dados. Recentemente, as grandes redes sociais forneceram fortes evidências de que os bancos de dados relacionais não são adequados para todos os possíveis cenários de negócios. Quando uma quantia (realmente) enorme de dados sem esquema chega até você, um banco de dados relacional pode às vezes se tornar um gargalo em vez de um condutor.

Você consegue imaginar o que poderia ser necessário para contar instantaneamente as curtidas em um comentário que amigos fizeram em uma postagem, em um banco de dados relacional abrangente com bilhões de registros no total? Além disso, restringir a definição de uma postagem para um esquema rígido é algo desafiador, para dizer o mínimo. Por uma simples questão de sobrevivência de negócios, as redes sociais evoluíram em algum momento e transferiram o foco do armazenamento para uma combinação de repositórios de dados relacionais e não relacionais, inaugurando oficialmente o negócio de dados poliglotas.

A lição fundamental aprendida com a arquitetura de software das redes sociais é que o armazenamento simples de dados que você tem nem sempre é a abordagem ideal em termos de negócios. Em vez de apenas armazenar os dados que estão com você, é preferível armazenar detalhes a respeito de um evento que ocorreu e os dados envolvidos com esse evento específico.

Neste artigo, examinarei primeiramente a base de negócios do fornecimento de eventos—usando eventos registrados em log como a fonte de dados primária dos aplicativos—e, em seguida, falarei sobre como atualizar as habilidades existentes de Criar, Ler, Atualizar, Excluir (CRUD) à luz dos eventos. Para deixar claro desde o princípio, a questão não é se você precisa do fornecimento de eventos. A questão é quando você precisa dele e como pode codificá-lo.

Em busca de modelos de dados dinâmicos

O uso de dados poliglotas é um assunto que está em alta hoje: bancos de dados relacionais para dados estruturados, repositórios de dados NoSQL para dados menos estruturados, dicionários de chave-valor para preferências e logs, bancos de dados de gráficos para relacionamentos e correlações. A introdução de diferentes modelos de armazenamento executados lado a lado é um passo na direção certa, mas, para mim, parece mais um remédio eficaz para um sintoma visível do que a cura para a doença real e subjacente.

O modelo relacional deve sua longa eficácia a um conjunto equilibrado de benefícios que oferece para a leitura e a gravação de dados. É fácil consultar e atualizar um modelo relacional, apesar de ele mostrar limites em algumas condições (muito) extremas. O desempenho geral diminui compreensivelmente em tabelas com milhões de registros e centenas de colunas. Além disso, o esquema de dados é fixo e é necessário conhecimento da estrutura do banco de dados para criar consultas rápidas e ad hoc. Em outras palavras, no mundo em que você codifica atualmente, um modelo abrangente, como o grande modelo relacional inicial, é uma restrição enorme que limita sua expressividade primeiramente e, mais tarde, seu poder de programação. No fim, um modelo é apenas um modelo; não é aquilo que você observa diretamente no mundo real. No mundo real, você não observa nenhum modelo. Em vez disso, um modelo é utilizado para encapsular um comportamento bem compreendido e repetível. Em última análise, você observa eventos no mundo real, mas espera armazenar informações relacionadas a um evento em um modelo restrito (relacional). Quando tiver dificuldade para fazer isso, considere modelos alternativos de armazenamento que apenas liberam algumas restrições de esquema e indexação.

Um modelo de armazenamento baseado em eventos

Por décadas, salvar apenas o estado atual das entidades foi algo útil e eficaz. O estado existente é substituído quando você salva um estado específico, perdendo todas as informações anteriores. Esse comportamento, por si só, não deve ser elogiado nem recriminado. Por anos, foi uma abordagem eficaz e teve grande aceitação. Apenas o domínio de negócios e os clientes podem realmente dizer se a perda de um estado anterior é aceitável. Os fatos indicam que, por muitos anos e para a maioria dos negócios, era aceitável. Essa tendência está mudando à medida que um número cada vez maior de aplicativos de negócios exige o acompanhamento do histórico completo das entidades de negócios. Aquilo que chamamos de CRUD há anos—as operações básicas para Criar, Ler, Atualizar, Excluir—e modelamos de tabelas relacionais básicas está se transformando em algo que podemos chamar genericamente de CRUD histórico. O CRUD histórico é simplesmente uma base de código CRUD na qual a implementação consegue acompanhar a lista inteira de alterações.

O mundo real está cheio de sistemas de linha de negócios (LoB) que, de alguma maneira, acompanham os eventos conforme acontecem no domínio. Essas classes de aplicativos existem há décadas—alguns chegaram a ser escritos em COBOL ou Visual Basic 6. Não há dúvidas, por exemplo, de que um aplicativo contábil acompanha todas as alterações que podem ocorrer em faturas, tais como alteração de data ou endereço, emissão de uma nota de crédito, etc. Em alguns cenários de negócios, o acompanhamento de eventos tem sido uma funcionalidade solicitada desde o princípio do software, muitas vezes se encaixando na categoria mais ampla da funcionalidade de auditoria.

Portanto, a auditoria de eventos de negócios não é um conceito novo em software. Durante décadas, as equipes de desenvolvimento resolveram o mesmo problema repetidas vezes, retrabalhando e refazendo técnicas conhecidas da melhor maneira que conseguiram encontrar. Hoje em dia, a velha prática de auditar eventos de negócios tem um nome mais atraente: Fornecimento de Eventos.

Como codificar seu caminho até os eventos de negócios

Digamos que você tem um aplicativo conceitualmente simples, como um aplicativo que permite que usuários reservem um recurso compartilhado (por exemplo, uma sala de reuniões). Quando uma usuária revisa o estado de uma reserva específica, pode se deparar não apenas com o estado atual da reserva, mas com a lista inteira de atualizações desde a criação. A Figura 1 esboça uma possível interface do usuário baseada em linha do tempo para a exibição.

Uma exibição baseada em linha do tempo para todo o histórico de uma reserva
Figura 1 A Uma exibição baseada em linha do tempo para todo o histórico de uma reserva

Como você conceberia um modelo de dados de reserva que se comporta como um CRUD histórico em vez de como um CRUD baseado em estado básico? Adicionar mais colunas à definição da tabela não é suficiente. A principal diferença entre um CRUD e um CRUD histórico é que, no último cenário, você deseja armazenar várias cópias da mesma entidade, uma para cada evento de negócios pelo qual ela passou em um momento específico. A Figura 2 mostra uma possível estrutura nova para a tabela de reserva do banco de dados relacional.

Um possível modelo de dados relacional para um aplicativo de CRUD histórico
Figura 2 Um possível modelo de dados relacional para um aplicativo de CRUD histórico

A tabela representada na Figura 2 tem o conjunto esperado de colunas que representam completamente o estado de uma entidade de negócios, além de algumas outras. No mínimo, você deseja ter uma coluna de chaves primárias para identificar com exclusividade uma linha na tabela. Em seguida, deseja ter uma coluna de carimbo de data/hora que indique o horário da operação no banco de dados ou qualquer carimbo de data/hora que faça sentido para o negócio. Em geral, a coluna tem a finalidade de associar uma data segura ao status da entidade. Por fim, você quer ter uma coluna que descreva o evento que foi registrado em log.

Ainda é uma tabela relacional e ainda gerencia a lista de reservas de que o aplicativo precisa. Nenhuma tecnologia nova foi adicionada, mas, conceitualmente, a tabela esquematizada na Figura 2 está muito distante de um CRUD clássico. É fácil adicionar registros à tabela nova. Basta preencher os registros e acrescentá-los à medida que é notificado de que aconteceu algo no sistema que precisa ser acompanhado. Já falamos sobre o C do CRUD; e as outras operações?

Atualizações e exclusões em um CRUD histórico

Depois que a tabela relacional clássica é transformada em uma tabela histórica baseada em eventos, a função e a relevância das atualizações e exclusões mudam significativamente. Em primeiro lugar, as atualizações desaparecem. Todas as atualizações ao estado lógico da entidade passam a ser implementadas como um novo registro acrescentado que acompanha o estado novo.

As exclusões são mais complicadas; a palavra final a respeito de como codificar está na lógica de negócios do domínio. Em um mundo ideal baseado em eventos, não existem exclusões. Os dados são apenas adicionados e, portanto, uma exclusão é simplesmente a adição de um novo evento que informa que a entidade deixou de existir logicamente. Entretanto, a remoção física de dados de uma tabela não é proibida por lei e ainda pode acontecer. Porém, observe que, em um cenário baseado em eventos, a entidade que deve ser removida não é composta por um único registro, mas consiste em uma coleção de registros, tal como representado na Figura 2. Se você decidir excluir uma entidade, precisará remover todos os eventos (e registros) relacionados a ela.

Como ler o estado de uma entidade

O maior benefício proporcionado pelo registro em log de eventos de negócios no seu aplicativo é que você nunca perde nada. Pode, potencialmente, acompanhar o estado de um sistema em um momento específico, descobrir a sequência exata de ações que levam a um estado específico e desfazer—na totalidade ou em parte—esses eventos. Isso estabelece as bases para business intelligence e cenários hipotéticos feitos por conta própria na análise de negócios. Para ser mais preciso, você não receberá automaticamente essas funcionalidades para uso imediato com seu aplicativo, mas já tem todos os dados de que necessita para desenvolver tais extensões do aplicativo existente.

A parte mais difícil de um CRUD histórico é a leitura de dados. Agora, você está acompanhando todos os eventos de negócios relevantes no sistema de reservas de amostra; contudo, não há nenhum local em que possa obter facilmente a lista completa de reservas permanentes. Não existe uma maneira rápida e fácil de saber, por exemplo, quantas reservas você tem na próxima semana. É aí que entram as projeções. A Figura 3 resume a arquitetura geral de um sistema que evolui de um CRUD básico para um CRUD histórico.

Arquitetura de um sistema de CRUD histórico
Figura 3 Arquitetura de um sistema de CRUD histórico

Um sistema baseado em eventos está inevitavelmente voltado para a implementação de uma separação clara entre a pilha de comando e a de consulta. Na camada de apresentação, o usuário dispara uma tarefa que atravessa a camada de aplicativo e de domínio, envolvendo todos os componentes de lógica de negócios no caminho. Um comando é o gatilho de uma tarefa de negócios que altera o estado atual do sistema, o que significa que é necessário confirmar alguma coisa que modifique logicamente o estado existente. Como mencionado, em um sistema baseado em eventos—mesmo quando o sistema é um sistema CRUD básico simples—alterar o estado significa adicionar um registro que indique que os usuários criaram ou atualizaram um registro específico. O bloco na Figura 3 rotulado “Repositório de Eventos” representa qualquer camada de código responsável pela persistência do evento. Em termos de terminologias concretas, o bloco Repositório de Eventos pode ser uma classe de repositório baseada no Entity Framework, bem como um wrapper em torno de m banco de dados de documentos (Banco de Dados de Documentos do Azure, RavenDB ou MongoDB). Ainda mais interessante, pode ser uma classe wrapper que usa a API de um repositório de eventos como EventStore ou NEventStore.

Em uma arquitetura baseada em eventos, o estado de uma entidade específica é calculado algoritmicamente a pedido. Esse processo se chama reprodução de eventos e consiste em consultar todos os eventos relacionados à entidade especificada e aplicar todos eles a uma instância nova da classe da entidade. A instância da entidade é atualizada no final do loop, porque tem o estado de uma instância nova que passou por todos os eventos registrados.

De forma geral, o processamento do log de eventos cria uma projeção de dados e extrai um modelo de dados dinâmicos de uma quantia de dados de nível inferior. É isso que chamamos de Modelo de Leitura na Figura 3. Com o mesmo log de eventos, é possível criar todos os modelos de dados que servem os vários front-ends. Para usar uma analogia de SQL, criar uma projeção de dados de eventos registrados em log equivale a criar uma exibição de uma tabela relacional.

Reproduzir eventos para determinar o estado atual de uma entidade para fins de consulta geralmente é uma opção viável, mas se torna cada vez menos eficaz conforme o número de eventos ou a frequência das solicitações cresce com o passar do tempo. Você não quer examinar milhares de registros de cada vez apenas para descobrir o saldo atual de uma conta bancária. Da mesma forma, não quer examinar centenas de eventos para apresentar a lista de reservas pendentes. A fim de resolver esses problemas, o modelo de leitura frequentemente assume a forma de uma tabela relacional clássica que é mantida programaticamente em sincronia com a tabela de eventos registrados em log.

Conclusão

A maioria dos aplicativos ainda pode ser amplamente catalogada como aplicativos de CRUD. O Facebook também pode ser apresentado como uma forma de CRUD, talvez um pouco maior do que a média. Falando sério, o último estado adequado conhecido ainda é suficiente para a maioria dos usuários; porém, o número de clientes para os quais essa exibição é insuficiente está crescendo. O próximo pode ser seu melhor cliente. Este artigo apresentou apenas os aspectos superficiais de um CRUD histórico. Apresentarei um exemplo concreto no próximo mês. Fique sintonizado!


Dino Espositoé o autor de “Microsoft .NET: Architecting Applications for the Enterprise” (Microsoft Press, 2014) e de “Modern Web Applications” (Microsoft Press, 2016). Evangelista técnico das plataformas .NET e Android no JetBrains e palestrante frequente em eventos do setor no mundo todo, Esposito compartilha sua visão de software em software2cents.wordpress.com e, no Twitter, em @despos.

Agradecemos ao seguinte especialista técnico da Microsoft pela revisão deste artigo: Jon Arne Saeteras