Começando do jeito certo com DDD mesmo para projetos simples
![]() |
Hoje eu vou mostrar como iniciar do jeito certo usando a abordagem do DDD mesmo para projetos simples. |
Na abordagem do
DDD você NÃO começa definindo relacionamentos entre entidades
para persistência.
Você começa entendendo o domínio, as
regras e definindo agregados.
Os
relacionamentos com FK de banco vêm depois — e muitos deles NÃO aparecem como navegação ORM.
Nota: Os relacionamentos do domínio são relacionamentos de colaboração e de consistência e não de FK de banco.
Vamos considerar a título de exercício um pequeno sistema onde tratamos com Pedido, ItemPedido, Produto e Categoria.
A primeira coisa a fazer é um ajuste de mentalidade.
❌ Mentalidade ORM / CRUD
“Tenho Pedido, Produto,
Categoria. Vou ligar tudo com FK e navegação.”
✅ Mentalidade
DDD
Quais são as regras?
Quem controla o quê?
O que
precisa ser consistente junto?”
DDD não elimina relacionamentos.
DDD
elimina relacionamentos desnecessários no domínio.
Tomando a decisão estrutural correta antes do código
Considerando este pequeno sistema, pensar em Bounded Contexts ajuda a evitar acoplamento — mas isso não significa necessariamente separar em projetos físicos.
Nota: Os Bounded Contexts (ou Contextos Delimitados) são fronteiras conceituais que definem o limite de aplicação de um modelo de domínio específico. Eles organizam um sistema complexo em partes menores e mais gerenciáveis, garantindo que termos e regras de negócio tenham significados consistentes e unívocos dentro de sua área.
Catálogo
Produto
Categoria
Pedidos
Pedido (Aggregate Root)
ItemPedido
Mesmo sendo simples,
essa separação mental já evita erros futuros.
Agora vejamos como modelar o DOMÍNIO (sem EF Core)
1 - Catálogo (Produto + Categoria)
Aqui relacionamento por objeto é aceitável, porque estão no mesmo contexto e compartilham a mesma linguagem, compartilham regras e evoluem juntos.
public sealed class Produto { public Guid Id { get; } public string Nome { get; private set; } public Money PrecoAtual { get; private set; } private readonly List<Categoria> _categorias = new(); public IReadOnlyCollection<Categoria> Categorias => _categorias; public void AssociarCategoria(Categoria categoria) { _categorias.Add(categoria); } } public sealed class Categoria { public Guid Id { get; } public string Nome { get; private set; } } |
AquiObserve que usamos classe sealed, propriedades com private set
Aqui não há problema nenhum em relacionamento por objeto.
2- Pedidos (Aggregate Root + entidade interna)
O
Pedido não deve depender diretamente da entidade Produto,
mas pode referenciar seu identificador ou um snapshot,
dependendo da necessidade do negócio.
public sealed class Pedido { private readonly List<ItemPedido> _itens = new(); public Guid Id { get; } public IReadOnlyCollection<ItemPedido> Itens => _itens; public void AdicionarItem(ProdutoSnapshot produto, Quantidade quantidade) { _itens.Add(new ItemPedido(produto, quantidade)); } } public sealed class ItemPedido { public Guid ProdutoId { get; } public string NomeProduto { get; } public Money PrecoUnitario { get; } public int Quantidade { get; } internal ItemPedido(ProdutoSnapshot produto, Quantidade quantidade) { ProdutoId = produto.Id; NomeProduto = produto.Nome; PrecoUnitario = produto.Preco; Quantidade = quantidade.Valor; } } |
Aqui não existe relacionamento ORM com Produto. Existe snapshot.
Nota: O uso de snapshot é indicado quando o histórico precisa ser preservado, como no caso de pedidos. Alterações futuras no produto não devem afetar pedidos já realizados.
Agora observe que ItemPedido não possui um Id ? Por quê ?
Porque ItemPedido NÃO é um Aggregate Root.
Ele é uma entidade interna do agregado Pedido.
No domínio:
ItemPedido só existe dentro de um
Pedido
Ele não faz sentido isolado
Ele não é acessado diretamente
Logo: ele não precisa de Id próprio no domínio.
Nota: O ItemPedido é uma entidade interna do agregado. Ele possui identidade dentro do agregado, mas essa identidade não precisa ser exposta publicamente nem ser global.
Mas e o banco de dados ?
Ótima pergunta — mas isso é
infraestrutura, não domínio.
No EF Core você pode:
Criar
uma PK técnica
Ou usar uma key shadow
Ou usar (PedidoId,
ProdutoId) como chave composta
Nada disso precisa aparecer no
domínio.
O DDD não exige que o modelo de domínio espelhe o modelo de
persistência.
O que seria errado aqui?
O que NÃO pode acontecer:
public Produto Produto { get; set; } // ❌ errado
public Categoria Categoria { get; set; } // ❌ errado
Porque aí:
Você estaria atravessando agregados ou atravessando
Bounded Contexts
Então... onde ficam os “relacionamentos” no EF Core ?
Eles ficam no
mapeamento, não no domínio.
EF Core mapeia:
Agregado → tabelas
Entidade interna → FK interna
Value Objects → colunas
Snapshot → dados duplicados
(intencional)
Exemplo: Pedido → ItemPedido (ok)
builder.HasMany(p=> p.Itens") .WithOne() .HasForeignKey("PedidoId"); |
Ou com tabela explícita, se quiser. O domínio não precisa saber como isso é persistido.
O que você NÃO faz (mesmo em sistema simples)
❌
Pedido → Produto (como navegação direta de entidade)
: Porque
Produto pertence ao
Catálogo e
Pedido ao
Contexto de Pedidos.
Referenciar
Produto faz o pedido depender de um modelo
mutável e externo, quebrando o
conceito de snapshot e a
autonomia do agregado.
❌ ItemPedido → Produto :
ItemPedido precisa representar
o produto como ele era no momento da
compra, não o produto atual. Referenciar
Produto
faz o histórico mudar retroativamente quando o catálogo muda.
❌ Navegação entre Bounded Contexts : Porque cada Bounded Context tem linguagem, regras e ritmo de evolução próprios. Navegação direta elimina a fronteira semântica e transforma os contextos em um único modelo acoplado.
❌ Domínio com virtual :
virtual existe para o ORM (proxy,
lazy loading), não para o negócio. Colocá-lo no domínio faz o modelo
existir para a infraestrutura,
não para representar regras. Evite usar virtual no domínio quando o objetivo
for atender ao ORM (lazy loading/proxy), pois isso introduz dependência de
infraestrutura.
❌ Domínio com [ForeignKey] :
[ForeignKey]
descreve como dados são persistidos, não como o negócio funciona. Usar isso no
domínio mistura persistência com regras, tornando o modelo dependente do banco.
❌ Modelo orientado a banco : Porque o banco otimiza
armazenamento e consulta,
enquanto o domínio otimiza significado
e comportamento. Quando o banco dita o modelo, o domínio vira um CRUD
anêmico.
Tudo que existe no domínio deve existir porque o negócio
precisa, não porque o banco ou o ORM
exigem.
Mesmo simples, isso cobra juros no futuro.
“Mas isso não é muito trabalho para sistema simples ?”
Resposta honesta:
✔ Sim, é um pouco mais de trabalho no começo
❌
Mas NÃO é
trabalho desnecessário
Porque você ganha:
Modelo claro
Regras explícitas
Domínio que cresce sem refatoração traumática
Separação
limpa de responsabilidades
Você está pagando um custo pequeno
agora para evitar um custo grande depois.
Conclusão final
Você PODE e DEVE :
Usar EF Core (quando
pertinente)
Ter um
domínio rico
Evitar modelo anêmico
Mesmo em sistemas simples
Mas você NÃO DEVE:
Começar pelo diagrama ER(Entidade-Relacionamento)
Deixar o ORM ditar o domínio
Confundir persistência com
modelagem
Modelar corretamente não é exagero, é reduzir o custo do
futuro.
E estamos
conversados...
"Miserável homem que eu sou! Quem me livrará do
corpo desta morte?
Dou graças a Deus por Jesus Cristo nosso Senhor. Assim que
eu mesmo com o entendimento sirvo à lei de Deus, mas com a carne à lei do
pecado."
Romanos
7:24,25
Referências:
NET - Unit of Work - Padrão Unidade de ...