CQRS e Event Sourcing em TypeScript: Arquitetura para sistemas complexos

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Por que aplicações CRUD comuns param de funcionar quando o negócio se torna complexo

\n\n

Você já se deparou com uma situação em que uma tarefa simples — "mostrar o histórico de alterações de um pedido" — se transforma em um inferno? Você vasculha o banco de dados, olha os logs, tenta reconstruir quem e quando alterou o status. E então chega uma solicitação: "Faça uma auditoria de todas as ações do usuário nos últimos três anos". E você percebe — a arquitetura atual não vai aguentar isso.

\n\n

Olá, meu nome é PythonLib, e hoje vamos analisar dois padrões poderosos que salvam nessas situações: CQRS (Command Query Responsibility Segregation) e Event Sourcing. Vamos escrever código em TypeScript que pode ser usado imediatamente em um projeto real.

\n\n

Se você desenvolve fintech, marketplaces, CRM ou qualquer sistema onde histórico e auditoria são importantes — este artigo é para você.

\n\n

O que é CQRS em palavras simples

\n\n

Imagine uma biblioteca. Normalmente, um bibliotecário entrega livros, os recebe e faz o registro. Mas quando o número de visitantes aumenta, forma-se uma fila. O CQRS propõe dividir as responsabilidades: um bibliotecário apenas entrega (comandos), o outro apenas recebe e registra (consultas).

\n\n

Na programação, isso significa separar os modelos de leitura e escrita. Em vez de um repositório monolítico, criamos:

\n\n
    \n
  • Command Model — para alterar dados (Create, Update, Delete)
  • \n
  • Query Model — para ler dados (Read)
  • \n
\n\n

Parece uma complicação? Sim, no início. Mas quando o sistema cresce, você ganha flexibilidade: pode otimizar a leitura separadamente da escrita, usar bancos de dados diferentes, cache.

\n\n

Exemplo em TypeScript: CQRS simples sem Event Sourcing

\n\n

Suponha que temos um sistema de gerenciamento de pedidos. Primeiro, definimos as entidades:

\n\n
// domain/order.ts\nexport interface Order {\n  id: string;\n  userId: string;\n  items: OrderItem[];\n  status: 'pending' | 'confirmed' | 'shipped' | 'cancelled';\n  total: number;\n  createdAt: Date;\n  updatedAt: Date;\n}\n\nexport interface OrderItem {\n  productId: string;\n  quantity: number;\n  price: number;\n}\n
\n\n

Agora, criamos um comando e uma consulta:

\n\n
// application/commands/create-order.command.ts\nexport class CreateOrderCommand {\n  constructor(\n    public readonly userId: string,\n    public readonly items: OrderItem[]\n  ) {}\n}\n\n// application/commands/create-order.handler.ts\nimport { CommandHandler, ICommandHandler } from '@nestjs/cqrs';\nimport { OrderRepository } from '../repositories/order.repository';\n\n@CommandHandler(CreateOrderCommand)\nexport class CreateOrderHandler implements ICommandHandler<CreateOrderCommand> {\n  constructor(private readonly orderRepo: OrderRepository) {}\n\n  async execute(command: CreateOrderCommand): Promise<string> {\n    const order: Order = {\n      id: uuid(),\n      userId: command.userId,\n      items: command.items,\n      status: 'pending',\n      total: command.items.reduce((sum, item) => sum + item.price * item.quantity, 0),\n      createdAt: new Date(),\n      updatedAt: new Date()\n    };\n    await this.orderRepo.save(order);\n    return order.id;\n  }\n}\n
\n\n

E para leitura — um manipulador separado:

\n\n
// application/queries/get-order.query.ts\nexport class GetOrderQuery {\n  constructor(public readonly orderId: string) {}\n}\n\n// application/queries/get-order.handler.ts\nimport { QueryHandler, IQueryHandler } from '@nestjs/cqrs';\nimport { OrderReadRepository } from '../repositories/order-read.repository';\n\n@QueryHandler(GetOrderQuery)\nexport class GetOrderHandler implements IQueryHandler<GetOrderQuery> {\n  constructor(private readonly readRepo: OrderReadRepository) {}\n\n  async execute(query: GetOrderQuery): Promise<Order | null> {\n    return this.readRepo.findById(query.orderId);\n  }\n}\n                

Blogs

Book Recommendations