Por que CQRS e Event Sourcing não são "para poucos"?
\n\nQuando ouvimos as expressões "CQRS" ou "Event Sourcing", muitos desenvolvedores JavaScript imaginam algo do mundo da arquitetura enterprise em Java ou C#. Esquemas complexos, eventos infinitos, armazenamentos estranhos... Mas e se eu disser que esses padrões podem (e devem!) ser usados em aplicações Node.js comuns?
\n\nNeste artigo, vamos entender como CQRS e Event Sourcing ajudam a resolver problemas reais: conflitos de dados, logging complexo, escalabilidade e depuração. E tudo isso — em JavaScript puro, sem mágica ou frameworks complexos.
\n\nO que é CQRS (Command Query Responsibility Segregation)?
\n\nImagine que sua aplicação é um restaurante. Os garçons (consultas) e os cozinheiros (comandos) trabalham no mesmo ambiente. Mais cedo ou mais tarde, eles começarão a atrapalhar uns aos outros. O CQRS propõe separar essas zonas: os garçons trabalham no salão (leitura), e os cozinheiros, na cozinha (escrita).
\n\nCQRS é o princípio de separar as operações em dois canais:
\n- \n
- Comandos (Commands) — alteram o estado do sistema (Create, Update, Delete) \n
- Consultas (Queries) — apenas leem dados (Read) \n
Em uma API REST clássica, geralmente usamos o mesmo objeto para leitura e escrita. Por exemplo, UserController contém os métodos GET /users/:id e POST /users. O CQRS propõe separá-los em camadas diferentes.
Exemplo simples de CQRS em JavaScript
\n\n// Comando (alteração de estado)\nclass CreateUserCommand {\n constructor(name, email) {\n this.name = name;\n this.email = email;\n }\n}\n\nclass UserCommandHandler {\n async handle(command) {\n // Validação, lógica de negócio, persistência\n return await db.users.insert({\n name: command.name,\n email: command.email,\n created_at: new Date()\n });\n }\n}\n\n// Consulta (apenas leitura)\nclass GetUserQuery {\n constructor(userId) {\n this.userId = userId;\n }\n}\n\nclass UserQueryHandler {\n async handle(query) {\n // Apenas leitura, nenhuma alteração!\n return await db.users.findOne({ _id: query.userId });\n }\n}\n\nParece que isso complica o código? Sim, à primeira vista. Mas quando sua aplicação crescer para 50+ endpoints, a separação de comandos e consultas vai te salvar de:
\n- \n
- Alterações acidentais de dados em requisições GET \n
- Problemas com cache (consultas são fáceis de cachear, comandos não) \n
- Conflitos durante o trabalho paralelo de vários desenvolvedores \n
Event Sourcing: registrando cada ação
\n\nAgora imagine que, em vez de armazenar o estado atual do usuário, você armazena todos os eventos que ocorreram com ele. Não "Ivan, 25 anos, Moscou", mas uma sequência:
\n- \n
- "Usuário criado: Ivan" \n
- "Usuário alterou a idade para 25" \n
- "Usuário mudou-se para Moscou" \n
Event Sourcing é um padrão onde cada alteração de estado é salva como um evento imutável. O estado atual é calculado "reproduzindo" todos os eventos.
\n\nImplementação de um Event Store em JavaScript
\n\nclass EventStore {\n constructor() {\n this.events = [];\n }\n\n append(aggregateId, event) {\n this.events.push({\n aggregateId,\n event,\n timestamp: new Date()\n });\n }\n\n getEvents(aggregateId) {\n return this.events.filter(e => e.aggregateId === aggregateId);\n }\n}\n\n// Reconstrução do estado a partir dos eventos\nfunction rebuildState(events) {\n return events.reduce((state, event) => {\n switch(event.event.type) {\n case 'USER_CREATED':\n return { ...state, name: event.event.name, age: 0 };\n case