Por que aplicações CRUD comuns param de funcionar quando o negócio se torna complexo
\n\nVocê 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\nOlá, 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\nSe você desenvolve fintech, marketplaces, CRM ou qualquer sistema onde histórico e auditoria são importantes — este artigo é para você.
\n\nO que é CQRS em palavras simples
\n\nImagine 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\nNa 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
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\nExemplo em TypeScript: CQRS simples sem Event Sourcing
\n\nSuponha 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\nAgora, 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\nE 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