Agregados
Definição
Seção intitulada “Definição”Para Eric Evans, o Agregado agrupa objetos associados que devem ser tratados como uma unidade para fins de consistência de dados. Ele define uma fronteira e uma raiz: a raiz é a única entidade acessível de fora; tudo dentro só é manipulado através dela.
Vaughn Vernon, no artigo Effective Aggregate Design, dá as regras práticas mais influentes: prefira agregados pequenos, referencie outros agregados por identidade e use consistência eventual entre agregados.
Agregado vs. Entidade
Seção intitulada “Agregado vs. Entidade”| Critério | Entidade | Agregado |
|---|---|---|
| Escopo | Objeto individual | Cluster de objetos com fronteira |
| Acesso | Direto | Somente via raiz |
| Consistência | De si mesmo | De toda a fronteira (invariantes que cruzam objetos) |
| Transação | — | Unidade transacional (1 agregado por transação) |
O problema resolvido
Seção intitulada “O problema resolvido”Controla onde ficam as invariantes que envolvem múltiplos objetos. Ex.: “a soma dos itens não pode exceder o limite do pedido”. Essa regra atravessa Pedido e ItemPedido — logo precisa de uma raiz (Pedido) que a proteja. Sem agregados, essas regras vazam para services e ficam impossíveis de garantir.
Regras de design (Vernon)
Seção intitulada “Regras de design (Vernon)”- Proteja invariantes verdadeiras dentro de uma fronteira de consistência. O que precisa ser consistente imediatamente fica no mesmo agregado.
- Prefira agregados pequenos. Grandes causam contenção, locks e problemas de performance/memória.
- Referencie outros agregados por ID, nunca por referência de objeto direta. (
clienteId, nãoCliente cliente.) - Uma transação modifica um único agregado. Consistência entre agregados é eventual, via Domain Events.
Trade-offs e quando NÃO usar
Seção intitulada “Trade-offs e quando NÃO usar”Agregar demais em nome de “conveniência” é o erro mais frequente. Se dois conceitos não compartilham invariantes imediatas, não devem estar no mesmo agregado. Em subdomínios genéricos ou consultas puras (CQRS lado leitura), a rigidez do agregado pode ser dispensada.
Exemplo em Java + Spring
Seção intitulada “Exemplo em Java + Spring”Raiz Pedido controlando o acesso aos itens, protegendo invariantes e referenciando o cliente por ID.
package com.loja.pedido.domain;
import com.loja.shared.domain.Dinheiro;import java.util.ArrayList;import java.util.Collections;import java.util.List;import java.util.Objects;
public class Pedido {
private static final int MAX_ITENS = 50;
private final PedidoId id; private final ClienteId clienteId; private final List<ItemPedido> itens; private final Dinheiro limite; private StatusPedido status;
public Pedido(ClienteId clienteId, Dinheiro limite) { this.id = PedidoId.novo(); this.clienteId = Objects.requireNonNull(clienteId); this.limite = Objects.requireNonNull(limite); this.itens = new ArrayList<>(); this.status = StatusPedido.ABERTO; }
public void adicionarItem(ProdutoId produtoId, int quantidade, Dinheiro precoUnitario) { garantirAberto(); if (itens.size() >= MAX_ITENS) { throw new IllegalStateException("Limite de itens atingido"); } ItemPedido novo = new ItemPedido(produtoId, quantidade, precoUnitario); Dinheiro totalProjetado = total().somar(novo.subtotal()); if (totalProjetado.maiorQue(limite)) { throw new IllegalStateException("Total excede o limite do pedido"); } itens.add(novo); }
public void removerItem(ProdutoId produtoId) { garantirAberto(); itens.removeIf(item -> item.produtoId().equals(produtoId)); }
public void confirmar() { garantirAberto(); if (itens.isEmpty()) { throw new IllegalStateException("Pedido sem itens"); } this.status = StatusPedido.CONFIRMADO; }
public Dinheiro total() { return itens.stream() .map(ItemPedido::subtotal) .reduce(Dinheiro.reais("0"), Dinheiro::somar); }
private void garantirAberto() { if (status != StatusPedido.ABERTO) { throw new IllegalStateException("Pedido nao esta aberto"); } }
public List<ItemPedido> itens() { return Collections.unmodifiableList(itens); }
public PedidoId id() { return id; }
public ClienteId clienteId() { return clienteId; }
public StatusPedido status() { return status; }}package com.loja.pedido.domain;
import com.loja.shared.domain.Dinheiro;import java.util.Objects;
public class ItemPedido {
private final ProdutoId produtoId; private final int quantidade; private final Dinheiro precoUnitario;
ItemPedido(ProdutoId produtoId, int quantidade, Dinheiro precoUnitario) { if (quantidade <= 0) { throw new IllegalArgumentException("Quantidade deve ser positiva"); } this.produtoId = Objects.requireNonNull(produtoId); this.quantidade = quantidade; this.precoUnitario = Objects.requireNonNull(precoUnitario); }
Dinheiro subtotal() { return precoUnitario.multiplicar(quantidade); }
ProdutoId produtoId() { return produtoId; }}classDiagram
class Pedido {
<<Aggregate Root>>
-PedidoId id
-ClienteId clienteId
-Dinheiro limite
-StatusPedido status
+adicionarItem()
+removerItem()
+confirmar()
+total() Dinheiro
}
class ItemPedido {
<<Value Object>>
-ProdutoId produtoId
-int quantidade
-Dinheiro precoUnitario
}
class ClienteId {
<<Value Object / ref>>
}
Pedido "1" *-- "*" ItemPedido : contém
Pedido ..> ClienteId : referencia por ID
Anti-padrões e erros comuns
Seção intitulada “Anti-padrões e erros comuns”- Agregados grandes demais: raiz que engloba meio domínio, gerando locks, contenção e carregamento pesado.
- Referência direta a outro agregado: guardar
Cliente clienteem vez deClienteId, acoplando ciclos de vida. - Modificar dois agregados na mesma transação: viola a regra de consistência; use Domain Events para consistência eventual.
- Acesso a membros internos por fora: manipular
ItemPedidosem passar pela raiz, furando invariantes.
Relações
Seção intitulada “Relações”- Composto por Entidades e Value Objects
- Persistido por Repositórios (um repositório por raiz de agregado)
- Publica Domain Events para consistência eventual entre agregados
- Criado por Factories (ver Design Estratégico e Tático)
- Parte de Domain-Driven Design
- Voltar para Home
Perguntas de revisão
Seção intitulada “Perguntas de revisão”1. O que a Raiz do Agregado garante?
Resposta
As invariantes de toda a fronteira do agregado. É a única porta de entrada: objetos internos só são manipulados através dela, o que mantém a consistência.
2. Por que referenciar outros agregados por ID?
Resposta
Para desacoplar ciclos de vida, manter agregados pequenos, evitar carregar grafos gigantes e respeitar a regra de uma transação por agregado. Segue a recomendação de Vaughn Vernon.
3. Quantos agregados devem ser modificados por transação?
Resposta
Um. A consistência dentro do agregado é imediata; entre agregados é eventual, coordenada por Domain Events.
4. Qual o risco de agregados grandes demais?
Resposta
Contenção e locks (muitos usuários alterando a mesma raiz), carregamento pesado em memória e transações longas. A recomendação é preferir agregados pequenos.