Aggregates, Entities e Value Objects

1. Fundamentos dos Blocos de Construção do DDD

1.1. O papel dos conceitos táticos no Design Dirigido por Domínio

No coração do Domain-Driven Design (DDD) estão os blocos de construção táticos: Aggregates, Entities e Value Objects. Enquanto os conceitos estratégicos (Bounded Contexts, Ubiquitous Language) definem o mapa do domínio, os conceitos táticos fornecem as ferramentas para implementar modelos de domínio ricos e expressivos. Eles transformam regras de negócio em código que comunica intenção, não apenas dados.

1.2. Diferença entre Modelos de Domínio e Modelos de Dados

Um erro comum é confundir modelo de domínio com modelo de dados. O modelo de dados reflete como as informações são armazenadas (tabelas, colunas, chaves estrangeiras). O modelo de domínio reflete como o negócio opera — inclui comportamentos, invariantes e regras. Aggregates, Entities e Value Objects pertencem ao modelo de domínio.

1.3. Critérios para escolher entre Entity e Value Object

A pergunta decisiva é: este objeto precisa de uma identidade contínua ao longo do tempo? Se sim, é Entity. Se não, e se o que importa são seus atributos, é Value Object.

  • Entity: tem ID próprio, mutável, rastreável.
  • Value Object: imutável, sem ID, comparado por valor.

2. Entities: Identidade e Ciclo de Vida

2.1. Definição de identidade única e continuidade

Uma Entity é definida por sua identidade, não por seus atributos. Um Cliente continua sendo o mesmo cliente mesmo que mude de endereço ou telefone. O que importa é o ClienteId único que o acompanha durante todo o ciclo de vida.

2.2. Mutabilidade controlada

Entities podem mudar de estado, mas essas mudanças devem ser controladas por métodos de domínio, nunca expondo setters públicos indiscriminadamente. A mutabilidade é uma necessidade — o cliente muda de endereço, o pedido é entregue — mas deve refletir regras de negócio.

2.3. Exemplo: Cliente

public class Cliente : Entity<ClienteId>
{
    public ClienteId Id { get; private set; }
    public string Nome { get; private set; }
    public Endereco Endereco { get; private set; }  // Value Object

    public void AtualizarEndereco(Endereco novoEndereco)
    {
        // Regra de negócio: endereço só pode ser alterado se não houver pedidos em aberto
        Endereco = novoEndereco;
    }
}

A identidade (ClienteId) persiste; o Endereco (Value Object) é substituído por completo.


3. Value Objects: Imutabilidade e Atributos Descritivos

3.1. Características essenciais

  • Sem identidade: dois Value Objects com os mesmos atributos são equivalentes.
  • Comparados por valor: new Money(100, "USD") == new Money(100, "USD").
  • Imutáveis: uma vez criados, não podem ser alterados. Se precisar de um valor diferente, crie um novo objeto.

3.2. Benefícios da imutabilidade

Imutabilidade traz segurança em ambientes concorrentes, previsibilidade (o objeto nunca muda inesperadamente) e reuso seguro. Value Objects podem ser compartilhados entre múltiplas Entities sem risco de efeitos colaterais.

3.3. Exemplo: Endereco

public class Endereco : ValueObject
{
    public string Rua { get; }
    public string Cidade { get; }
    public string Cep { get; }

    public Endereco(string rua, string cidade, string cep)
    {
        Rua = rua;
        Cidade = cidade;
        Cep = cep;
    }

    protected override IEnumerable<object> GetEqualityComponents()
    {
        yield return Rua;
        yield return Cidade;
        yield return Cep;
    }
}

4. Aggregates: Transações e Consistência

4.1. Definição de Aggregate como unidade de consistência transacional

Um Aggregate é um cluster de objetos de domínio (Entities e Value Objects) tratados como uma unidade. Toda operação que modifica o estado interno deve preservar as invariantes do Aggregate. O Aggregate Root é a única entrada para o mundo externo.

4.2. Regra fundamental: referências externas apenas pelo ID

Nunca mantenha referências diretas a objetos internos de um Aggregate. Referencie apenas o ID do Aggregate Root. Isso garante que modificações sempre passem pelo Root, que pode validar regras.

4.3. Exemplo: Pedido como Aggregate Root

public class Pedido : AggregateRoot<PedidoId>
{
    private List<ItemPedido> _itens;
    public PedidoId Id { get; private set; }
    public ClienteId ClienteId { get; private set; }
    public Money ValorTotal { get; private set; }
    public IReadOnlyList<ItemPedido> Itens => _itens.AsReadOnly();

    public void AdicionarItem(ProdutoId produtoId, int quantidade, Money precoUnitario)
    {
        var item = new ItemPedido(produtoId, quantidade, precoUnitario);
        _itens.Add(item);
        RecalcularTotal();
    }

    private void RecalcularTotal()
    {
        ValorTotal = new Money(_itens.Sum(i => i.Subtotal.Valor), "BRL");
    }
}

ItemPedido é uma Entity interna (tem identidade dentro do pedido), Money e ProdutoId são Value Objects.


5. Relações e Interações entre os Três Conceitos

5.1. Como Entities e Value Objects coexistem dentro de um Aggregate

Dentro de um Aggregate, Entities podem conter Value Objects, e Value Objects podem referenciar IDs de Entities (nunca a Entity diretamente). O Aggregate Root coordena todas as interações.

5.2. Regras de navegação

  • Fora do Aggregate: só se comunica com o Root, por ID.
  • Dentro do Aggregate: o Root pode acessar livremente seus objetos internos, mas eles não devem ser expostos para alteração direta (apenas leitura).

5.3. Exemplo prático: Compra

public class Compra : AggregateRoot<CompraId>
{
    public CompraId Id { get; private set; }
    public ClienteId ClienteId { get; private set; }  // Referência por ID
    public Money ValorPago { get; private set; }      // Value Object
    public DateTime DataCompra { get; private set; }

    // ItemCompra é uma Entity interna
    private List<ItemCompra> _itens;
}

6. Implicações para a Arquitetura de Software

6.1. Impacto no design de repositórios

Repositórios persistem Aggregates inteiros, não objetos individuais. Um PedidoRepository carrega e salva o Pedido completo com seus itens, garantindo consistência transacional. Value Objects são serializados como parte da Entity que os contém.

6.2. Estratégias de versionamento de Value Objects

Como Value Objects são imutáveis, novas versões substituem completamente a anterior. Em bancos relacionais, isso significa atualizar colunas; em bancos de eventos, registrar o novo valor como um evento de domínio.

6.3. Relação com Bounded Contexts

Aggregates vivem dentro de Bounded Contexts. Um mesmo conceito (ex: Cliente) pode ser Entity em um contexto e Value Object em outro. O Aggregate define os limites de consistência dentro de cada contexto.


7. Padrões e Anti-Padrões Comuns

7.1. Armadilha: criar Entity para tudo

Nem tudo precisa de identidade. Se um conceito é apenas um conjunto de atributos que descrevem outro objeto, use Value Object. Exemplo: Cor, CPF, Endereco.

7.2. Erro: Value Objects mutáveis

Quebrar a imutabilidade de um Value Object introduz bugs sutis. Se um Money(100, "USD") pode ter seu valor alterado, duas referências ao mesmo objeto podem divergir inesperadamente.

7.3. Boas práticas: Aggregates pequenos e coesos

Aggregates grandes demais prejudicam performance e concorrência. Prefira Aggregates pequenos que mantenham apenas as invariantes estritamente necessárias. Se duas entidades podem ser atualizadas independentemente, provavelmente são Aggregates separados.


Referências