Pular para o conteúdo
.NETEntidades × Value Objects
Módulo 07Domain-Driven Design (DDD)

Entidades × Value Objects

avancado 40 min de leitura·Atualizado · .NET 9
Resumo

No DDD tático, alguns conceitos têm identidade própria (entidades) e outros são definidos apenas pelos seus atributos (value objects). Escolher o tipo certo evita bugs de igualdade e centraliza validação.

1. Objetivos de Aprendizagem#

  • Distinguir entidade (identidade) de value object (valor).
  • Modelar um value object imutável com record.
  • Mover validação de dados para dentro do value object.

2. Pré-requisitos#

  • DDD e linguagem ubíqua (Aula 44.1).
  • Records e imutabilidade (Aula 6.1).

3. Conceito#

  • Entidade: tem identidade que persiste no tempo. Dois produtos com o mesmo nome e preço são diferentes se têm Id diferente. A igualdade é por identidade (Id). O Produto da Aula 5.1 é uma entidade.
  • Value Object (VO): não tem identidade; é definido pelo conjunto de atributos. Dois valores "R$ 10,00" são o mesmo valor. A igualdade é por valor. VOs são imutáveis e substituíveis: em vez de alterar, você troca por outro.

Sinais de que algo é um value object: você o descreveria por seus atributos (não por "qual"), não se importa com o histórico dele, e faz sentido ele ser imutável. Exemplos clássicos: Dinheiro, Email, Endereço, Intervalo de datas.

O ganho: um VO valida a si mesmo no nascimento e não pode entrar em estado inválido depois. decimal preco aceita -5; um VO Dinheiro não.

4. Mão na Massa#

Vamos transformar o decimal Preco da Aula 5.1 num value object Dinheiro. recordigualdade por valor e imutabilidade de graça (Aula 6.1):

C#
public sealed record Dinheiro
{
    public decimal Valor { get; }
    public string Moeda { get; }

    public Dinheiro(decimal valor, string moeda = "BRL")
    {
        if (valor < 0)
            throw new ArgumentException("Dinheiro não pode ser negativo.", nameof(valor));
        if (string.IsNullOrWhiteSpace(moeda) || moeda.Length != 3)
            throw new ArgumentException("Moeda deve ter 3 letras (ISO 4217).", nameof(moeda));

        Valor = decimal.Round(valor, 2);
        Moeda = moeda.ToUpperInvariant();
    }

    // Operações do domínio devolvem um NOVO Dinheiro (imutável).
    public Dinheiro Aplicar(decimal percentual) =>
        new(Valor * (1 + percentual), Moeda);

    public override string ToString() => $"{Moeda} {Valor:N2}";
}

Agora a entidade Produto usa o VO no lugar do decimal:

C#
public class Produto
{
    public Guid Id { get; }                 // identidade -> é ENTIDADE
    public string Nome { get; private set; }
    public Dinheiro Preco { get; private set; }   // value object

    public Produto(string nome, Dinheiro preco)
    {
        if (string.IsNullOrWhiteSpace(nome))
            throw new ArgumentException("Nome é obrigatório.", nameof(nome));
        Id = Guid.NewGuid();
        Nome = nome;
        Preco = preco;                       // já nasce válido: Dinheiro se valida
    }

    public void Reajustar(decimal percentual) =>
        Preco = Preco.Aplicar(percentual);   // troca por um novo Dinheiro
}

5. Exemplo Completo#

C#
var p = new Produto("Teclado", new Dinheiro(120m));
p.Reajustar(0.10m);
Console.WriteLine(p.Preco);            // BRL 132,00

var a = new Dinheiro(10m);
var b = new Dinheiro(10m);
Console.WriteLine(a == b);             // True — igualdade por valor (record)

// new Dinheiro(-5m);                  // lança: nunca existe um preço negativo

A validação de "preço" mora num único lugar — o VO. Qualquer produto, pedido ou desconto que use Dinheiro herda essa garantia.

6. Boas Práticas e Armadilhas#

FaçaEvite
Modelar conceitos sem identidade como VO imutávelUsar tipos primitivos "crus" (primitive obsession): decimal, string para tudo
Validar dentro do construtor do VOEspalhar if (preco < 0) por vários serviços
Devolver um novo VO em operaçõesMutar o VO no lugar (quebra a imutabilidade)
Atenção

value objects não têm tabela própria. Com EF Core, mapeie-os como owned types (OwnsOne) ou conversores de valor. Veremos a camada de infraestrutura na Aula 44.5.

7. Segurança e Produção#

  • Um VO que se valida elimina uma classe inteira de bugs: estados inválidos deixam de ser representáveis. "Tornar o estado ilegal irrepresentável" é uma das ideias mais valiosas do DDD tático.

8. Exercícios#

  • Fácil: crie um VO Email que valida formato no construtor.
  • Médio: dê a Dinheiro um método Somar(Dinheiro) que rejeita moedas diferentes.
  • Desafio: identifique dois usos de primitive obsession no seu projeto e proponha VOs.

9. Resumo#

Entidades têm identidade e igualdade por Id; value objects são definidos pelo valor, imutáveis e auto-validados. Trocar primitivos por VOs centraliza regras e evita estados inválidos.

10. Próximos Passos#

Como agrupar entidades e VOs numa unidade consistente: agregados.

11. Referências#

44-02 — Entidades × Value Objects | Curso ASP.NET Core