Executando verificação de segurança...
2

CQRS e Event Sourcing com TypeScript: Separando Leitura, Escrita e Histórico de Verdade

O problema que CRUD esconde de você

Uma tabela orders com 15 colunas. Alguém muda o status de um pedido de "pendente" para "cancelado". O UPDATE sobrescreve o valor anterior. Três meses depois, o financeiro pergunta: "esse pedido foi cancelado antes ou depois do pagamento ser confirmado?". Você não sabe. O banco guarda o estado atual, não a sequência de fatos que levou até ele.

CQRS (Command Query Responsibility Segregation) separa o modelo de escrita do modelo de leitura. Event Sourcing grava cada mudança de estado como um evento imutável, em vez de sobrescrever linhas. Juntos, resolvem o problema de auditoria e permitem otimizar leitura e escrita de forma independente.

Mas a combinação tem custo real: complexidade de infraestrutura, eventual consistency entre modelos, e uma curva de aprendizado que pega de surpresa quem trata os dois como "CRUD com eventos". Este post implementa ambos do zero em TypeScript, com código que roda, e mostra onde a coisa quebra.

CQRS vs. CRUD vs. Event Sourcing: quando usar cada um

CritérioCRUD tradicionalCQRS (sem Event Sourcing)CQRS + Event Sourcing
Complexidade de implementaçãoBaixaMédiaAlta
Auditoria completa de mudançasNão (precisa de tabela auxiliar)Não nativamenteSim, por design
Performance de leitura otimizávelLimitada (mesmo modelo)Alta (modelo de leitura separado)Alta
ConsistênciaImediataEventual ou imediataEventual
Quando faz sentidoApps com menos de 5 entidades de domínio, CRUD puroLeitura e escrita com padrões de acesso muito diferentesDomínios onde o histórico de mudanças É o negócio (financeiro, logística, healthcare)
Quando é excessoNunca é excesso para CRUD simplesApps pequenas sem diferença de carga leitura/escritaQualquer sistema onde você não precisa reconstruir estado a partir de eventos

Se seu sistema é um cadastro de usuários com login e perfil, CQRS + Event Sourcing é overengineering. Se você está modelando transações financeiras, movimentação de estoque ou workflows com múltiplos estados intermediários, a combinação se paga.

Modelando eventos de domínio

Eventos descrevem fatos que já aconteceram. O nome sempre no passado: OrderCreated, PaymentConfirmed, OrderCancelled. Comece pelos tipos:

// src/events/order-events.ts

export interface DomainEvent {
  readonly eventId: string;
  readonly aggregateId: string;
  readonly eventType: string;
  readonly timestamp: Date;
  readonly version: number;
  readonly payload: Record<string, unknown>;
}

export interface OrderCreated extends DomainEvent {
  eventType: "OrderCreated";
  payload: {
    customerId: string;
    items: Array<{ productId: string; quantity: number; priceInCents: number }>;
    totalInCents: number;
  };
}

export interface PaymentConfirmed extends DomainEvent {
  eventType: "PaymentConfirmed";
  payload: {
    paymentId: string;
    amountInCents: number;
    method: "credit_card" | "pix" | "boleto";
  };
}

export interface OrderCancelled extends DomainEvent {
  eventType: "OrderCancelled";
  payload: {
    reason: string;
    cancelledBy: string;
  };
}

export type OrderEvent = OrderCreated | PaymentConfirmed | OrderCancelled;

O campo version é o número sequencial do evento dentro do aggregate. Ele serve para detectar conflitos de concorrência: se dois comandos tentam gravar a versão 3 ao mesmo tempo, o segundo falha. Sem isso, você perde eventos.

Event Store: gravando fatos imutáveis

O event store é um append-only log. Nada é atualizado, nada é deletado. A implementação mínima em memória (para entender o contrato) e depois a interface para persistência real:

// src/store/event-store.ts

import { randomUUID } from "node:crypto";
import type { DomainEvent } from "../events/order-events.js";

export class EventStore {
  // Map de aggregateId para lista ordenada de eventos
  private streams = new Map<string, DomainEvent[]>();

  async append(
    aggreg

---

Leia o artigo completo em [https://www.vivodecodigo.com.br/backend/cqrs-event-sourcing-typescript-guia-pratico](https://www.vivodecodigo.com.br/backend/cqrs-event-sourcing-typescript-guia-pratico)
Carregando publicação patrocinada...