Modelagem de Domínio com Event Sourcing e Projeções Desacopladas em Bancos de Dados Relacionais
Descubra como construir sistemas resilientes usando Event Sourcing em bancos relacionais tradicionais, separando o histórico de eventos das tabelas de leitura para máxima performance e auditoria.
Resumo
- O armazenamento imutável de eventos garante que nenhuma transação passada seja perdida ou modificada acidentalmente no sistema.
- Bancos relacionais comuns como PostgreSQL suportam perfeitamente a persistência de eventos usando tabelas append-only e colunas JSONB.
- Projeções desacopladas transformam o histórico complexo em visões otimizadas para leitura rápida sem travar o fluxo principal.
- A eventual consistency exige que a interface do usuário lide com pequenas latências de atualização de forma elegante e transparente.
- Migrações de esquema tornam-se triviais porque as projeções podem ser recalculadas do zero a partir do log original de eventos.
O Desafio da Persistência Tradicional em Sistemas Complexos
Quando construímos softwares focados em negócios, o padrão comum é atualizar o estado atual de um registro diretamente em uma tabela do banco de dados. Na prática, isso significa que se um usuário altera o endereço de entrega, sobrescrevemos o dado antigo, apagando o passado para sempre. Em sistemas financeiros ou de e-commerce, perder o histórico de como chegamos a um determinado estado gera problemas graves de auditoria e dificulta a descoberta de bugs. O modelo tradicional de CRUD (Criar, Ler, Atualizar e Deletar) funciona bem para cadastros simples, mas sofre gargalos quando a complexidade do domínio aumenta e precisamos saber exatamente o que aconteceu, quando aconteceu e quem autorizou cada mudança.
Para resolver essa limitação, engenheiros adotam uma abordagem baseada em eventos, onde a verdade absoluta do sistema não é o estado atual, mas a sequência cronológica de fatos que ocorreram ao longo do tempo. Na prática, isso significa que cada ação do usuário gera um evento imutável, como 'PedidoCriado' ou 'PagamentoAprovado', que é gravado em uma tabela de log. Esse fluxo unidirecional elimina disputas de concorrência destrutivas e transforma o banco de dados em um diário de bordo confiável. A grande vantagem é que qualquer auditoria futura torna-se trivial, pois basta ler o diário do início ao fim para reconstruir o cenário exato de qualquer operação passada.
Implementando Event Sourcing com Bancos Relacionais Tradicionais
Existe um mito de que o armazenamento de eventos exige bancos de dados exóticos ou complexos baseados em grafos e streams. Na prática, sistemas relacionais maduros como PostgreSQL ou MySQL lidam muito bem com essa carga quando estruturados de forma correta. O segredo técnico consiste em criar uma tabela de eventos append-only, ou seja, onde inserções são permitidas, mas atualizações e deleções são estritamente proibidas por regras de negócio e restrições do banco. Cada linha armazena o identificador da entidade, o tipo do evento, a versão sequencial para controle de concorrência otimista e o payload completo serializado em um formato flexível.
Para ilustrar essa estrutura na prática, veja como podemos modelar a tabela de eventos e a recuperação de estado em uma aplicação típica usando uma abordagem relacional pura. O código abaixo demonstra a estrutura básica em SQL e uma rotina conceitual para reconstrução do estado atual através da leitura sequencial dos eventos acumulados na base.
CREATE TABLE event_store (
event_id UUID PRIMARY KEY,
aggregate_id UUID NOT NULL,
event_type VARCHAR(255) NOT NULL,
version INT NOT NULL,
payload JSONB NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT unique_aggregate_version UNIQUE (aggregate_id, version)
);
CREATE INDEX idx_aggregate_stream ON event_store (aggregate_id, version);O uso de colunas do tipo JSONB em bancos relacionais modernos remove a rigidez excessiva dos esquemas tradicionais, permitindo que diferentes tipos de eventos armazenem estruturas de dados variadas sem exigir migrações complexas de colunas a cada nova funcionalidade. A restrição de unicidade na combinação do identificador da entidade com a versão garante que duas transações concorrentes nunca consoguem gravar o mesmo número de versão, prevenindo condições de corrida e inconsistências silenciosas.
O Papel Crucial das Projeções Desacopladas
Guardar apenas eventos resolve o problema da auditoria e da segurança dos dados, mas gera um novo obstáculo para a leitura. Se precisarmos calcular o saldo atual de uma conta ou a lista de pedidos ativos de um cliente, ler milhares de eventos e somá-los toda vez que a tela carregar seria inviável do ponto de vista de performance. Na prática, isso significa que precisamos separar a escrita da leitura. Entram em cena as projeções desacopladas, que são tabelas ou modelos de dados otimizados exclusivamente para consulta rápida, alimentados de forma assíncrona pelo fluxo de eventos.
Quando um novo evento é inserido na tabela de eventos principal, um mecanismo de mensageria interna ou um processo em segundo plano captura esse fato e atualiza as tabelas de projeção correspondentes. Na prática, isso significa que a tela do usuário consulta uma tabela relacional comum e rápida, enquanto a lógica pesada de negócio acontece em background. Se a projeção sofrer algum dano ou precisar de uma nova coluna para atender a um relatório gerencial, podemos simplesmente apagá-la e reprocessar todos os eventos desde o dia zero, reconstruindo a visão sem perder nenhuma informação histórica preciosa.
Gerenciando a Consistência Eventual na Prática
Separar o armazenamento de eventos das tabelas de leitura traz uma mudança importante na forma como o software lida com o tempo e com o feedback para o usuário. Como a projeção é atualizada de forma assíncrona, existe um intervalo de milissegundos ou segundos entre a gravação do evento e a efetivação da mudança na tela de consulta. Na prática, isso significa que a aplicação precisa lidar com a consistência eventual, projetando interfaces que informem sutilmente ao usuário que seus dados estão sendo processados, em vez de travar a navegação inteira aguardando a resposta síncrona do banco.
Para mitigar essa percepção de lentidão, as equipes de engenharia costumam empregar técnicas no frontend, como a atualização otimista de interface, onde o cliente simula o sucesso da ação imediatamente enquanto o backend processa o evento real em segundo plano. Caso ocorra uma falha no processamento do evento, o sistema emite um sinal corretivo que reverte a interface de forma controlada. Essa resiliência operacional exige disciplina arquitetural, mas recompensa a equipe com um sistema altamente escalável, capaz de absorver picos de tráfego intensos sem derrubar o banco de dados relacional principal.
Considerações Finais sobre Escalabilidade e Manutenção
Adotar modelagem de domínio baseada em eventos e projeções desacopladas em bancos relacionais não é uma bala de prata, mas sim uma decisão arquitetural deliberada para sistemas que exigem rastreabilidade rigorosa e alta resiliência. A complexidade inicial de configurar o armazenamento de eventos e gerenciar as projeções assíncronas compensa amplamente ao longo do ciclo de vida do software, facilitando refatorações, auditorias e escalabilidade horizontal. Ao dominar esses conceitos utilizando ferramentas relacionais que sua equipe já conhece e domina, você elimina a necessidade de infraestruturas exóticas e mantém o controle total sobre a arquitetura dos seus dados.