Entidades × Value Objects
ResumoNo 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
Iddiferente. A igualdade é por identidade (Id). OProdutoda 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. record dá igualdade por valor e imutabilidade de graça (Aula 6.1):
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:
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#
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ça | Evite |
|---|---|
| Modelar conceitos sem identidade como VO imutável | Usar tipos primitivos "crus" (primitive obsession): decimal, string para tudo |
| Validar dentro do construtor do VO | Espalhar if (preco < 0) por vários serviços |
| Devolver um novo VO em operações | Mutar o VO no lugar (quebra a imutabilidade) |
Atençãovalue 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
Emailque valida formato no construtor. - Médio: dê a
Dinheiroum métodoSomar(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#
- Microsoft Learn — Implementando value objects
- Microsoft Learn — Owned entity types no EF Core