Pular para o conteúdo

CQRS

O agregado existe para proteger invariantes na escrita: carregar o cluster inteiro, decidir, salvar consistente. Uma tela de listagem não muda estado nenhum — forçá-la a passar pelo agregado significa hidratar grafos inteiros para jogar fora, e distorcer o modelo com métodos de consulta que não protegem nada.

CQRS oficializa a assimetria: comandos passam pelo modelo rico; consultas têm caminho próprio, otimizado para leitura, devolvendo DTOs no formato da tela.

graph LR
    CMD["Comando<br/>confirmar pedido"] --> UC["Use case"] --> AGG["Agregado<br/>Pedido"] --> DB[("Banco")]
    QRY["Consulta<br/>listar resumos"] --> QH["Query handler"] --> DB
    QH --> DTO["PedidoResumo (DTO)"]
Nível O que separa Quando
0 Nada: mesmo modelo para tudo CRUDs simples
1 Consultas dedicadas no mesmo banco (DTO via SQL/projeção) A maioria dos sistemas com telas de listagem e relatórios
2 Modelo de leitura materializado (projeções alimentadas por Domain Events) Leitura com formato muito diferente da escrita, alto volume
3 Event Sourcing + stores separados Casos raros com exigência de auditoria total/replay; custo alto

O nível 1 resolve a maior parte da dor e é o referenciado por Repositórios (“consultas complexas podem escapar do repositório de escrita”) e Agregados.

A consulta é uma porta de saída de leitura, com DTO próprio — o agregado Pedido nunca é carregado:

package com.loja.pedido.application;
import java.math.BigDecimal;
import java.util.List;
public record PedidoResumo(String pedidoId, String status, BigDecimal total, int quantidadeItens) {}
public interface ConsultaPedidos {
List<PedidoResumo> resumosDoCliente(String clienteId);
}
package com.loja.pedido.infrastructure.persistence;
import com.loja.pedido.application.ConsultaPedidos;
import com.loja.pedido.application.PedidoResumo;
import java.util.List;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
@Component
public class ConsultaPedidosJdbc implements ConsultaPedidos {
private final JdbcTemplate jdbcTemplate;
public ConsultaPedidosJdbc(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public List<PedidoResumo> resumosDoCliente(String clienteId) {
return jdbcTemplate.query("""
SELECT p.id, p.status, p.total, COUNT(i.id) AS itens
FROM pedido p JOIN item_pedido i ON i.pedido_id = p.id
WHERE p.cliente_id = ?
GROUP BY p.id, p.status, p.total
""",
(rs, linha) -> new PedidoResumo(
rs.getString("id"),
rs.getString("status"),
rs.getBigDecimal("total"),
rs.getInt("itens")),
clienteId);
}
}

Não há perda em pular o agregado: consulta não precisa de invariante. E a interface continua sendo uma driven port (Ports) — o SQL fica no adapter.

  • CQRS = event sourcing + 2 bancos: confundir o padrão com sua versão mais cara. Os níveis 1-2 não exigem nada disso.
  • Query passando pelo agregado por dogma: hidratar o cluster inteiro para montar uma listagem paginada.
  • Escrita via modelo de leitura: atualizar direto a projeção/DTO, contornando as invariantes do agregado — o lado de escrita continua sendo o único que muda estado.
  • Aplicar em CRUD trivial: sem assimetria real entre leitura e escrita, a separação é cerimônia.

1. O que exatamente o CQRS separa?

Resposta

A responsabilidade de escrita (comandos que passam pelos agregados e protegem invariantes) da de leitura (consultas que produzem DTOs otimizados para a tela, sem carregar agregados).

2. CQRS exige event sourcing ou dois bancos?

Resposta

Não. O nível mais útil e comum é o 1: consultas dedicadas no mesmo banco, com SQL/projeções próprias. Event sourcing e stores separados são o nível mais caro, para casos específicos.

3. Por que consultas não precisam passar pelo agregado?

Resposta

Porque o agregado existe para proteger invariantes em mudanças de estado. Uma consulta não muda nada — carregá-lo é custo sem benefício, e ainda distorce o modelo com métodos de leitura.

4. Qual o risco de escrever pelo modelo de leitura?

Resposta

Contornar as invariantes: a projeção/DTO não valida nada. Toda mudança de estado deve continuar entrando pelo lado de comando, via raiz de agregado.