Ferramentas & Produtividade

O que Todo Dev Deve Saber sobre Event Sourcing

7 min de leitura

O que Todo Dev Deve Saber sobre Event Sourcing

O que é Event Sourcing Event Sourcing é um padrão arquitetural onde você armazena todas as mudanças de estado de uma aplicação como uma sequência imutável de eventos. Em vez de persistir apenas o estado atual (como faz um banco de dados tradicional), você registra cada ação que ocorreu. Isso significa que o histórico completo está sempre disponível, e você pode reconstruir qualquer estado anterior simplesmente reproduzindo os eventos. A principal diferença em relação ao paradigma CRUD tradicional é que aqui não você atualiza um registro — você registra que algo aconteceu. Essa abordagem traz benefícios como auditoria natural, debugging facilitado, e possibilidade de projeções múltiplas do mesmo dado. No entanto, adiciona complexidade operacional que deve ser bem compreendida antes de aplicar em produção. Por que usar Event Sourcing? Auditoria e Conformidade: Cada mudança fica registrada permanentemente com timestamp. Para setores como financeiro e healthcare, isso é ouro puro. Debugging e Troubleshooting: Você pode reproduzir exatamente o que aconteceu em

<h2>O que é Event Sourcing</h2>

<p>Event Sourcing é um padrão arquitetural onde você armazena todas as mudanças de estado de uma aplicação como uma sequência imutável de eventos. Em vez de persistir apenas o estado atual (como faz um banco de dados tradicional), você registra cada ação que ocorreu. Isso significa que o histórico completo está sempre disponível, e você pode reconstruir qualquer estado anterior simplesmente reproduzindo os eventos.</p>

<p>A principal diferença em relação ao paradigma CRUD tradicional é que aqui não você <em>atualiza</em> um registro — você <em>registra que algo aconteceu</em>. Essa abordagem traz benefícios como auditoria natural, debugging facilitado, e possibilidade de projeções múltiplas do mesmo dado. No entanto, adiciona complexidade operacional que deve ser bem compreendida antes de aplicar em produção.</p>

<h3>Por que usar Event Sourcing?</h3>

<p><strong>Auditoria e Conformidade</strong>: Cada mudança fica registrada permanentemente com timestamp. Para setores como financeiro e healthcare, isso é ouro puro. <strong>Debugging e Troubleshooting</strong>: Você pode reproduzir exatamente o que aconteceu em um dado momento. <strong>CQRS (Command Query Responsibility Segregation)</strong>: Separar escrita (comandos) de leitura (queries) fica naturalmente elegante. <strong>Event-Driven Architecture</strong>: Permite comunicação assincrônica entre microsserviços através de eventos.</p>

<h2>Arquitetura e Componentes Principais</h2>

<p>Um sistema com Event Sourcing típico possui: o <strong>Event Store</strong> (banco imutável que armazena eventos), o <strong>Aggregate</strong> (entidade que processa comandos e emite eventos), <strong>Projections</strong> (visões processadas dos eventos para consulta rápida), e <strong>Event Handlers</strong> (reagem aos eventos para efeitos colaterais).</p>

<p>O fluxo é simples: usuário executa uma ação → comando é validado → aggregate processa e emite evento → evento é persistido → projeções são atualizadas → subscribers reagem. A garantia de imutabilidade do event store é crítica: você nunca deleta ou altera eventos, apenas adiciona novos.</p>

<h3>Event Store</h3>

<p>É o coração do sistema. Você precisa garantir que cada evento seja persistido de forma ordenada e recuperável. Existem soluções especializadas como EventStoreDB e Axon Framework, mas você também pode implementar com PostgreSQL ou MongoDB.</p>

<pre><code class="language-javascript">// Exemplo simples de Event Store em Node.js com PostgreSQL

const { Pool } = require(&#039;pg&#039;);

class EventStore {

constructor() {

this.pool = new Pool({

connectionString: &#039;postgresql://user:password@localhost/eventstore&#039;

});

}

async appendEvent(aggregateId, eventType, eventData, metadata = {}) {

const query = `

INSERT INTO events (aggregate_id, event_type, event_data, metadata, created_at)

VALUES ($1, $2, $3, $4, NOW())

RETURNING *

`;

const result = await this.pool.query(query, [

aggregateId,

eventType,

JSON.stringify(eventData),

JSON.stringify(metadata)

]);

return result.rows[0];

}

async getEventsByAggregateId(aggregateId) {

const query = &#039;SELECT * FROM events WHERE aggregate_id = $1 ORDER BY created_at ASC&#039;;

const result = await this.pool.query(query, [aggregateId]);

return result.rows;

}

}

module.exports = EventStore;</code></pre>

<h3>Aggregates e Projeções</h3>

<p>Um <strong>Aggregate</strong> é uma entidade que encapsula lógica de negócio. Ele recebe comandos, aplica regras e emite eventos. Uma <strong>Projeção</strong> é uma visão construída a partir de eventos, otimizada para leitura rápida.</p>

<pre><code class="language-javascript">// Aggregate de Conta Bancária

class BankAccountAggregate {

constructor(accountId) {

this.accountId = accountId;

this.balance = 0;

this.status = &#039;active&#039;;

this.changes = [];

}

deposit(amount) {

if (amount &lt;= 0) throw new Error(&#039;Invalid amount&#039;);

this.recordEvent(&#039;MoneyDeposited&#039;, { amount, timestamp: new Date() });

}

withdraw(amount) {

if (amount &gt; this.balance) throw new Error(&#039;Insufficient funds&#039;);

this.recordEvent(&#039;MoneyWithdrawn&#039;, { amount, timestamp: new Date() });

}

recordEvent(eventType, eventData) {

this.changes.push({ eventType, eventData });

this.applyEvent(eventType, eventData);

}

applyEvent(eventType, eventData) {

if (eventType === &#039;MoneyDeposited&#039;) {

this.balance += eventData.amount;

} else if (eventType === &#039;MoneyWithdrawn&#039;) {

this.balance -= eventData.amount;

}

}

loadFromHistory(events) {

events.forEach(event =&gt; {

this.applyEvent(event.event_type, JSON.parse(event.event_data));

});

}

}

// Projeção para leitura rápida

class AccountBalanceProjection {

constructor() {

this.accounts = new Map();

}

handleMoneyDeposited(accountId, eventData) {

const account = this.accounts.get(accountId) || { balance: 0 };

account.balance += eventData.amount;

account.lastUpdated = eventData.timestamp;

this.accounts.set(accountId, account);

}

handleMoneyWithdrawn(accountId, eventData) {

const account = this.accounts.get(accountId) || { balance: 0 };

account.balance -= eventData.amount;

account.lastUpdated = eventData.timestamp;

this.accounts.set(accountId, account);

}

getBalance(accountId) {

const account = this.accounts.get(accountId);

return account ? account.balance : 0;

}

}

module.exports = { BankAccountAggregate, AccountBalanceProjection };</code></pre>

<h2>Desafios e Boas Práticas</h2>

<p>Event Sourcing não é uma solução universal. O principal desafio é a <strong>eventual consistency</strong>: quando você escreve um evento, as projeções podem levar tempo para atualizar. Isso exige que sua aplicação aceite inconsistência temporária. Além disso, o <strong>crescimento do event store</strong> é inevitável — com milhões de eventos, reconstruir um aggregate do zero fica lento. A solução é usar <strong>snapshots</strong>: periodicamente salve o estado calculado para não precisar reprocessar todos os eventos.</p>

<p>Outro ponto crítico: <strong>versionamento de eventos</strong>. Requisitos mudam, e você não pode simplesmente deletar eventos antigos. Implemente versioning desde o início, com lógica de migração para lidar com esquemas desatualizados.</p>

<pre><code class="language-javascript">// Boas práticas: Snapshot e Versionamento

class SnapshotStore {

constructor() {

this.snapshots = new Map();

}

saveSnapshot(aggregateId, version, state) {

this.snapshots.set(aggregateId, { version, state, timestamp: new Date() });

}

getSnapshot(aggregateId) {

return this.snapshots.get(aggregateId);

}

}

// Handler com tratamento de versões

function handleEvent(eventType, eventVersion, eventData) {

if (eventType === &#039;MoneyDeposited&#039;) {

if (eventVersion === 1) {

// Lógica legada

return { amount: eventData.amount };

} else if (eventVersion === 2) {

// Nova lógica com taxa

return { amount: eventData.amount, fee: eventData.fee || 0 };

}

}

}</code></pre>

<h2>Quando Usar Event Sourcing</h2>

<p>Aplique Event Sourcing em domínios onde <strong>auditoria é crítica</strong> (financeiro, saúde, compliance), <strong>histórico é valor</strong> (análise temporal, machine learning), ou <strong>comunicação entre sistemas</strong> é complexa (microsserviços). Evite em aplicações CRUD simples, sistemas com latência crítica (real-time gaming), ou quando o overhead não se justifica.</p>

<p>Um e-commerce, por exemplo, se beneficia: rastrear pedidos, devoluções, reembolsos. Uma aplicação de to-do list? Provavelmente não.</p>

<h2>Conclusão</h2>

<p>Event Sourcing é poderoso, mas não é bala de prata. O grande aprendizado é entender que <strong>estado é derivado de eventos, não o inverso</strong>. Isso muda fundamentalmente como você pensa sobre persistência. Segundo, reconheça que <strong>eventual consistency é uma troca aceitável</strong> por auditoria, escalabilidade e simplicidade operacional. Terceiro, <strong>comece simples</strong>: implemente event store básico antes de adicionar snapshots, projeções complexas ou CQRS completo.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://eventstore.com/docs/" target="_blank" rel="noopener noreferrer">EventStoreDB Documentation</a></li>

<li><a href="https://learn.microsoft.com/en-us/azure/architecture/patterns/event-sourcing" target="_blank" rel="noopener noreferrer">Microsoft - Event Sourcing Pattern</a></li>

<li><a href="https://martinfowler.com/eaaDev/EventSourcing.html" target="_blank" rel="noopener noreferrer">Martin Fowler - Event Sourcing</a></li>

<li><a href="https://docs.axoniq.io/reference-guide/" target="_blank" rel="noopener noreferrer">Axon Framework Guide</a></li>

<li><a href="https://www.oreilly.com/library/view/building-event-driven-microservices/9781492057321/" target="_blank" rel="noopener noreferrer">Building Event-Driven Microservices - O&#039;Reilly</a></li>

</ul>

Comentários

Mais em Ferramentas & Produtividade

DevOps: Do Básico ao Avançado
DevOps: Do Básico ao Avançado

O que é DevOps e Por Que Importa DevOps é uma cultura que unifica desenvolvim...

Boas Práticas de Clean Architecture para Times Ágeis
Boas Práticas de Clean Architecture para Times Ágeis

Clean Architecture para Times Ágeis: Fundamentação e Benefícios Clean Archite...

Como Usar Docker e Kubernetes em Produção
Como Usar Docker e Kubernetes em Produção

Docker em Produção: Containerização Eficiente Docker é essencial para qualque...