Introdução: Quando o CRUD deixa de dar conta
\n\nImagine que você está escrevendo um sistema para negociações em bolsa de valores. A cada segundo — milhares de ordens. Cada ordem não é apenas "inserir um registro". É verificação de saldo, recálculo de portfólio, auditoria, notificações. E agora imagine que você precisa não apenas registrar o estado atual, mas também recuperar o que aconteceu às 14:35:22.003 de ontem. O CRUD (Create, Read, Update, Delete) comum quebra aqui. Bem-vindo ao mundo do CQRS e Event Sourcing em Java.
\n\nEsses dois padrões não são apenas palavras da moda. São ferramentas que permitem construir sistemas com alta performance, auditoria completa e capacidade de "rebobinar o tempo". Neste artigo, vamos analisar como implementar CQRS (Command Query Responsibility Segregation) e Event Sourcing em Java, usando frameworks modernos e código limpo. Sem enrolação, com exemplos.
\n\nO que é CQRS em palavras simples
\n\nCQRS é a separação de responsabilidades entre comandos (escrita) e consultas (leitura). No CRUD clássico, você usa o mesmo modelo para leitura e escrita. No CQRS, você cria dois modelos diferentes: um para alterar dados (comandos), outro para extraí-los (consultas).
\n\nPor que isso é necessário?
\n- \n
- Performance: Leitura e escrita escalam independentemente. Você pode ter 10 réplicas para leitura e 2 para escrita. \n
- Segurança: Comandos podem verificar regras de negócio, enquanto consultas apenas fornecem dados. \n
- Flexibilidade: Para leitura, você pode usar visões desnormalizadas; para escrita, visões normalizadas. \n
Exemplo de código: Separação de comandos e consultas
\n\nSuponha que temos um sistema de gerenciamento de pedidos. Em vez de fazer OrderService.save(order), separamos:
// Comando — é uma intenção de alterar o estado\npublic class CreateOrderCommand {\n private final String orderId;\n private final String customerId;\n private final List<OrderItem> items;\n\n // construtor, getters...\n}\n\n// Manipulador do comando\n@Component\npublic class CreateOrderCommandHandler {\n @Autowired\n private OrderRepository orderRepository;\n\n public void handle(CreateOrderCommand command) {\n // Validação de regras de negócio\n if (command.getItems().isEmpty()) {\n throw new IllegalArgumentException("Order must have at least one item");\n }\n Order order = new Order(command.getOrderId(), command.getCustomerId(), command.getItems());\n orderRepository.save(order);\n }\n}\n\n// Consulta — é a obtenção de dados\npublic class GetOrderQuery {\n private final String orderId;\n // construtor, getters...\n}\n\n// Manipulador da consulta\n@Component\npublic class GetOrderQueryHandler {\n @Autowired\n private OrderReadRepository orderReadRepository;\n\n public OrderDTO handle(GetOrderQuery query) {\n // Leitura direta de um banco de dados otimizado\n return orderReadRepository.findById(query.getOrderId());\n }\n}\n\nObserve: OrderRepository (escrita) e OrderReadRepository (leitura) são interfaces diferentes. Elas podem apontar para bancos de dados diferentes ou esquemas diferentes.
Event Sourcing: Armazenamos não o estado, mas a história
\n\nEvent Sourcing é um padrão no qual salvamos não o estado atual de um objeto, mas a sequência de eventos que levaram a esse estado. Em vez de "Pedido nº 123: status = DELIVERED", armazenamos: "Evento 1: Pedido criado", "Evento 2: Item adicionado", "Evento 3: Pedido pago", "Evento 4: Pedido entregue".
\n\nComo funciona em Java
\n\nCada evento é um objeto imutável (immutable). Nós os salvamos em um Event Store (banco de dados especializado). O estado atual do agregado é calculado reproduzindo (replaying) todos os eventos.
\n\n// Evento base\npublic interface Do