DDD não é o que você esta pensando !!!


     Neste artigo veremos porque muita gente acha que esta usando DDD só porque criou camadas e organizou o código em pastas com nomes como Domain, Application, Infrastructure e API.

Para não tornar o artigo longo e chato e vou assumir um cenário bem ingênuo mas que vai servir ao nosso propósito. Imagine que você tem a seguinte definição de classes em um sistema qualquer onde tem que tratar com Produto e Categoria:



Você vai encontrar este exemplo de código em 90% dos exemplos na internet definindo o relacionamento entre Produto e Categoria :

public class Categoria
{
    public int Id { get; set; }
    public string Nome { get; set; }
    public virtual ICollection<Produto> Produtos { get; set; }
}
public class Produto
{
    public int Id { get; set; }
    public string Nome { get; set; }
    public decimal Preco { get; set; }
    public int Estoque { get; set; }
    public int CategoriaId { get; set; }
    public virtual Categoria Categoria { get; set; }

No sistema foi adotada uma arquitetura em camadas com organização em pastas como : Domain, Application, Infrastructure e API.  Até aqui tudo bem. O problema é você afirmar que esta usando a abordagem DDD neste sistema se baseando nisso.

Vamos por partes...

Primeiro: esse modelo está correto em DDD ?

Resposta curta e honesta:

❌ Não. Esse modelo NÃO está correto sob a perspectiva de DDD.
✔ Ele está correto sob a perspectiva de ORM / EF Core.

E isso é uma diferença crítica.

Mas porque não é DDD ?

Vamos analisar o código com lupa :

❌ Problemas do ponto de vista do DDD

Entidades com setters públicos
Qualquer código pode quebrar invariantes
Temos um relacionamento bidirecional rico
Um agregado “enxerga” o outro por inteiro
Temos uma mistura de conceito de navegação com domínio
O domínio passa a existir para o ORM
Categoria e Produto estão acoplados fortemente
A Mudança em um impacta diretamente o outro
O Modelo é orientado à persistência
Não existe comportamento definido


Isso é o que chamamos de modelo anêmico orientado a banco.

O erro conceitual mais importante aqui :

“Este código esta definindo duas classes Categoria e Produto que possuem um relacionamento muitos para muitos”

Mas na abordagem do DDD, a pergunta correta é:

Categoria e Produto pertencem ao mesmo Bounded Context ?

O que é Bounded Context (Contexto Delimitado)?

No mundo real, uma palavra pode significar coisas diferentes dependendo de onde você está. No desenvolvimento de software, o Bounded Context é a fronteira lógica onde um modelo de domínio específico e sua Linguagem Ubíqua são aplicáveis e válidos.

O Problema: Tentar criar um modelo único para a empresa inteira gera classes gigantescas e confusas.

A Solução: Dividimos o sistema em contextos menores.

No contexto de Vendas, um Produto tem preço e estoque.
No contexto de Logística, o mesmo Produto tem peso, dimensões e tipo de embalagem.

Porque :
   Se pertencem ao MESMO contexto, NÃO existe ACL (Anti-Corruption Layer)
   Se pertencem a contextos diferentes, NÃO pode existir relacionamento direto

O que é ACL (Anti-Corruption Layer)?

A Camada Anticorrupção(ACL) é uma camada de tradução. Ela é usada quando o seu sistema (o novo, limpo, seguindo DDD) precisa conversar com outro sistema (um legado, uma API externa ou outro Bounded Context) que possui um modelo de dados diferente.

Sua função: Impedir que os conceitos e a "sujeira" do modelo externo "corrompam" o seu modelo de domínio interno.

Como funciona: Ela traduz as solicitações do seu contexto para o formato que o outro entende e vice-versa, mantendo o seu domínio isolado e puro.

Nota : ACL é um padrão de design crucial utilizado para servir como uma ponte de comunicação tradutora e isolante entre dois ou domínios que possuem modelos de domínio ou linguagens ubíquas diferentes

Por que isso muda o relacionamento Categoria x Produto?

Como você bem pontuou, a decisão de design depende da fronteira:

Mesmo Contexto (Ex: Catálogo): Se Categoria e Produto vivem juntos, o relacionamento é direto. O foco aqui é como o negócio organiza os itens.

Contextos Diferentes (Ex: Marketing x Estoque): Se Categoria pertence ao Marketing e Produto ao Inventário, eles não se conhecem diretamente via código/banco. A comunicação acontece via Identificadores (IDs) ou Eventos de Domínio, muitas vezes mediada por uma ACL para garantir que uma mudança no Marketing não quebre a lógica do Estoque.

Cenário A — Categoria e Produto NO MESMO Bounded Context

Exemplo: Gestão de Catálogo

Nesse caso:

❌ NÃO existe ACL
❌ NÃO existe Published Language (Padrão de integração utilizado para permitir a comunicação entre diferentes Contextos Delimitados)
❌ NÃO existe Snapshot (refere-se a uma representação serializada do estado de um Agregado (Aggregate) em um determinado momento no tempo)

Porque não há integração entre contextos.

Modelagem DDD correta (simplificada)

public 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 class Categoria
{
    public Guid Id { get; }
    public string Nome { get; private set; }

Observe:

Sem navegação ORM
Sem bidirecionalidade
Sem dependência de infraestrutura

DDD não começa modelando relacionamentos (1:N, N:N).
DDD começa modelando responsabilidades e invariantes.

Cenário B — Produto e Categoria em CONTEXTOS DIFERENTES

Aqui Produto e Categoria NÃO estão em contextos diferentes entre si
Eles estão juntos no Catálogo
O “contextos diferentes” aqui é entre o BC Catálogo e o BC Pedidos

“Onde entra a ACL aqui?”

A ACL é uma camada que protege o seu domínio de modelos, conceitos e regras externas. Lembrando que ACL não é: DTO, API Client , Repositório, Adapter técnico, Mapper genérico (AutoMapper) e vive na camada de aplicação e não no domínio.

A ACL NÃO entra entre Categoria e Produto
Ela entra quando OUTRO contexto consome dados do Catálogo

Exemplo: Pedidos consumindo Produto do Catálogo

Catálogo publica (Published Language):

public sealed record ProdutoCatalogoDto(
    Guid Id,
    string Nome,
    decimal Preco
);

Pedidos recebe isso via ACL:

public sealed class ProdutoCatalogoAcl
{
   public ProdutoSnapshot Traduzir(ProdutoCatalogoDto dto)
   {
         return new ProdutoSnapshot(
               dto.Id,
               dto.Nome,
               new PrecoUnitario(dto.Preco)
            );
      }
}

E o domínio de Pedidos usa:   pedido.AdicionarItem(produtoSnapshot);

Pedidos nunca aceita Produto do Catálogo
Pedidos só aceita o que ELE entende

Grave esta regra:

A ACL só existe ENTRE Bounded Contexts.
Nunca dentro de um mesmo contexto.

Se você tem:

Categoria ↔ Produto no mesmo BC → sem ACL
Catálogo → Pedidos → com ACL
Clientes → Pedidos → com ACL

A ACL só existe quando atravessamos uma fronteira de Bounded Context.
Dentro do mesmo contexto, não há ACL.
Entre contextos diferentes, a ACL é obrigatória para proteger o domínio.

E estamos conversados...  

"Porque somos feitura sua, criados em Cristo Jesus para as boas obras, as quais Deus preparou para que andássemos nelas"
Efésios 2:10

Referências:


José Carlos Macoratti