Porque seu CRUD não é DDD (mesmo c/uma bela estrutura de pastas)


     Neste artigo vou comparar CRUD com DDD, e, o objetivo não é dizer que CRUD é “errado” mas mostrar o que você ganha e o que você perde em cada abordagem.

Vamos definir um cenário igual para ambas as abordagens:  Estamos tratando com Pedido, ItemPedido e Produto onde nosso objetivo será criar um pedido e adicionar um item com um preço do produto. Um cenário bem simples.

Regras reais do negócio:

O preço do item deve ser o preço no momento da compra
O pedido não pode mudar se o preço do produto mudar depois
O pedido controla seus próprios itens



Modelo 1 - CRUD /ORM Centric

Você vai encontrar este exemplo de código em 90% dos exemplos na internet:

public class Pedido
{
    public int Id { get; set; }
    public List<ItemPedido> Itens { get; set; } = new();
}
public class ItemPedido
{
    public int Id { get; set; }
    public int PedidoId { get; set; }
    public int ProdutoId { get; set; }
    public Produto Produto { get; set; }
    public int Quantidade { get; set; }
}
public class Produto
{
    public int Id { get; set; }
    public string Nome { get; set; }
    public decimal Preco { get; set; }
}

Agora o caso de uso:

var pedido = db.Pedidos.Find(pedidoId);
var produto = db.Produtos.Find(produtoId);
pedido.Itens.Add(new ItemPedido
{
    Produto = produto,
    Quantidade = 2
});
db.SaveChanges();

Problemas (reais) deste código:

❌ Regra 1 quebrada:  pedido.Itens[0].Produto.Preco

Se o preço do produto mudar amanhã → o pedido muda retroativamente

❌ Regra 2 inexistente

Pedido não protege invariantes
Qualquer código pode alterar tudo

❌ Pedido não controla nada

Ele é só um “saco de dados”
Toda regra está espalhada

Quando ESSE modelo é aceitável ?

✔ CRUD simples
✔ Sistemas administrativos
✔ Baixo risco
✔ Histórico não crítico

Aqui usar DDD é desnecessário, e isso não esta errado.

MODELO 2 — DDD DE VERDADE

Vamos definir um Modelo de domínio que não seja anêmico.

public sealed class Pedido
{
    private readonly List<ItemPedido> _itens = new();
    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 PrecoUnitario PrecoUnitario { get; }
    public Quantidade Quantidade { get; }
    internal ItemPedido(ProdutoSnapshot produto, Quantidade quantidade)
    {
        ProdutoId = produto.Id;
        NomeProduto = produto.Nome;
        PrecoUnitario = produto.Preco;
        Quantidade = quantidade;
    }
}
public sealed record ProdutoSnapshot(
    Guid Id,
    string Nome,
    PrecoUnitario Preco
); 

O Snapshot é uma cópia imutável do estado de algo no momento em que um fato de negócio acontece.

Um pedido não referencia o produto. Ele registra como o produto era quando foi comprado.

Caso de uso (Application Layer)

var produtoDto = catalogoApi.ObterProduto(produtoId);
var snapshot = acl.Traduzir(produtoDto);
var pedido = repo.ObterPorId(pedidoId);
pedido.AdicionarItem(snapshot, new Quantidade(2));
repo.Salvar(pedido);

Entendendo o código:

1- catalogoApi.ObterProduto(produtoId)
O que está acontecendo aqui?

Você está saindo do Bounded Context de Pedidos e consultando o Catálogo, que é um Upstream.

Esse método:

NÃO retorna uma entidade Produto
NÃO retorna algo do domínio de Pedidos
Retorna Published Language do Catálogo

 2 - acl.Traduzir(produtoDto)
Aqui está o coração do DDD

Essa linha é onde DDD realmente acontece.

A ACL responde à pergunta:  “O que desse dado externo faz sentido dentro do meu domínio?”

O que a ACL faz (e só ela pode fazer)

✔ Valida se o dado externo é aceitável
✔ Traduza nomes e conceitos
✔ Cria Value Objects
✔ Cria o Snapshot
✔ Remove tudo que não importa

O que  esse modelo garante:

Regra 1 protegida

Preço é copiado
Histórico preservado

Regra 2 protegida

Pedido controla seus itens
Ninguém altera estado direto

Linguagem do negócio explícita

pedido.AdicionarItem(...)

Não :

pedido.Itens.Add(...)

Comparação direta (sem dó nem piedade)

Aspecto CRUD DDD
Facilidade inicial ⭐⭐⭐⭐⭐ ⭐⭐
Curva de aprendizado Baixa Alta
Proteção de regras
Histórico consistente
Domínio expressivo
Escalabilidade conceitual
Custo inicial Baixo Alto
Custo a longo prazo Alto Baixo

O ponto mais importante (que quase ninguém diz)

DDD não substitui CRUD.
DDD substitui sistemas que quebram quando crescem.

Se o sistema:
   não cresce
   não muda
   não tem regra crítica

CRUD é a escolha correta.

Mas se:
   ras egras evoluem
   o histórico importa
   múltiplos times mexem
   o domínio é estratégico
   o CRUD cobra a conta depois.

CRUD otimiza o hoje.
DDD otimiza o amanhã.

E estamos conversados...  

"Todavia para nós há um só Deus, o Pai, de quem é tudo e em quem estamos; e um só Senhor, Jesus Cristo, pelo qual são todas as coisas, e nós por ele."
1 Coríntios 8:6

Referências:


José Carlos Macoratti