CRUD Tradicional vs Abordagem DDD - I


     Neste artigo vou fazer uma comparação com a implementação de um CRUD tradicional e a abordagem do Domain Driven Design.

A maioria dos desenvolvedores .NET aprende EF Core antes de dominar conceitos de modelagem. Eles priorizam relacionamentos entre entidades no banco de dados em vez de regras de negócio, confundindo o modelo de domínio com o modelo de persistência.

Muitos veem DDD como algo "grande demais" ou "engenharia excessiva" para projetos pequenos. Esta série de artigos aborda essas mentalidades para mostrar que DDD pode ser simples e aplicável mesmo em sistemas modestos.

Neste artigo vou iniciar um comparativo prático entre duas abordagens de modelagem: 

1- Abordagem CRUD Tradicional: Como a maioria dos desenvolvedores .NET aprendem, focando em ORM e banco de dados primeiro

2- Abordagem DDD (Domain-Driven Design): Começando pelo domínio de negócio, focando em regras e invariantes



Assim vamos comparar duas mentalidades

CRUD Tradicional Abordagem DDD

Começa pelo banco

Começa pelo negócio

Entidades anêmicas (sem lógica)

Entidades ricas (com comportamento)

ORM dita o modelo

Domínio dita o ORM

Foco em relacionamentos

Foco em invariantes (regras imutáveis)

Pedido → Cliente → Endereço → Produto

Agregados e fronteiras claras

Entretanto é bom deixar claro que eu não vou aprofundar no DDD estratégico nem vou implementar  todos os patterns do 'Blue Book" nem vou criar uma arquitetura enterprise completa.

Esta série de artigos é apenas uma introdução prática ao pensamento do domínio onde vamos comparar lado a lado duas abordagens onde iremos tratar com um sistema de de vendas simplificado.

Cenário do Sistema de Vendas

Vamos modelar um sistema que gerencia :

- Pedidos e seus itens
- Produtos e categorias
- Clientes e endereços
- Pagamentos

Onde teremos as seguintes regras de negócio:

-Um Pedido possui itens
-O total do pedido depende dos itens
-O total do pedido não pode ser negativo
-O preço do produto pode mudar, mas o pedido não
-O preço do produto não pode ser negativo
-O cliente possui um endereço
-O pagamento pertence ao pedido
-Um pedido não pode ser alterado após pago

Usaremos C# com .NET, EF Core para persistência e SQLite como banco de dados (leve e fácil para ilustração). Ao final, você verá que DDD promove responsabilidade, clareza e intenção no código, sem complicar desnecessariamente.

O problema real como a maioria aprende

A maioria dos desenvolvedores .NET inicia sua jornada focando no EF Core e na criação de tabelas, priorizando a infraestrutura antes mesmo de entender as regras de negócio. Essa abordagem é impulsionada por tutoriais que buscam resultados rápidos, fazendo com que o código seja moldado para satisfazer o banco de dados, e não a lógica da aplicação.

Essa mentalidade gera o chamado "modelo anêmico", onde as classes são apenas depósitos de dados sem inteligência própria. Embora o CRUD tradicional funcione perfeitamente em projetos pequenos e no início do desenvolvimento, ele acaba espalhando as regras por controladores e serviços, criando um sistema frágil e difícil de evoluir.

A seguir vou mostrar que você não está programando errado, apenas aprendeu as etapas na ordem inversa. Ao reconhecer que essa prática é um padrão comum do mercado e não uma falha pessoal, você estará pronto para inverter essa lógica e colocar o domínio no centro do seu desenvolvimento.

 “O DDD não nasce para sistemas grandes. Ele nasce para evitar decisões ruins repetidas muitas vezes.”

Implementando o CRUD Tradicional

O objetivo desta etapa é construir um modelo que espelhe a prática comum do aluno, onde a priorização do banco de dados dita as regras do sistema. Nessa abordagem, a modelagem é orientada pela estrutura das tabelas e pela definição explícita de relacionamentos, fazendo com que o esquema de dados molde o formato final da aplicação.

Nessa mentalidade, o EF Core atua como o guia da arquitetura, transformando entidades em meros "DTOs persistentes". As classes tornam-se sacos de dados anêmicos e sem comportamentos internos, servindo apenas para transportar informações de forma direta entre a interface e o banco de dados.

Para isso podemos criar um projeto Web API adicionando os seguintes pacotes Nuget:

 - Microsoft.EntityFrameworkCore, Microsoft.EntityFrameworkCore.Sqlite e Microsoft.EntityFrameworkCore.

A seguir podemos criar uma pasta Entities e nesta pasta criar as seguintes entidades:

public class Pedido
{
    public int Id { get; set; }
    public int ClienteId { get; set; }
    public Cliente Cliente { get; set; }
    public int EnderecoId { get; set; }
    public Endereco Endereco { get; set; }
    public decimal Total { get; set; }
    public ICollection<ItemPedido> Itens { get; set; } = new List<ItemPedido>();
    public Pagamento Pagamento { get; set; }
} 

Neste código temos que :

- Pedido conhece Cliente
- Pedido conhece Endereço
- Pedido conhece Produto indiretamente
- Total é um campo mutável
- Não temos nenhuma regra de negócio

public class ItemPedido
{
    public int Id { get; set; }
    public int PedidoId { get; set; }
    public Pedido Pedido { get; set; }
    public int ProdutoId { get; set; }
    public Produto Produto { get; set; }
    public int Quantidade { get; set; }
    public decimal PrecoUnitario { get; set; }
}

Neste código:

- ItemPedido depende de Produto
- Preço vem do Produto atual
- Nenhuma proteção contra mudança de preço

using System;
public class Pagamento
{
    public int Id { get; set; }
    public DateTime DataPagamento { get; set; }
    public int PedidoId { get; set; }
    public Pedido Pedido { get; set; }
}

Neste código, Pagamento tem uma propriedade PedidoId (chave estrangeira, indicando uma relação de banco de dados).  Pagamento também tem uma propriedade Pedido (propriedade de navegação, que permite acessar o objeto Pedido associado).

Isso significa que Pagamento não pode existir ou ser totalmente funcional sem uma referência a Pedido. Por exemplo, para processar um pagamento, o sistema provavelmente precisa validar ou consultar o pedido relacionado.

Evidência: Sem Pedido, a propriedade Pedido seria null ou inválida, tornando Pagamento incompleto. Isso cria uma dependência estrutural.

using System.Collections.Generic;
public class Produto
{
    public int Id { get; set; }
    public string Nome { get; set; }
    public decimal Preco { get; set; }
    public int CategoriaId { get; set; }
    public Categoria Categoria { get; set; }
    public ICollection<ItemPedido> Itens { get; set; } = new List<ItemPedido>();

No código, Produto depende de Categoria: A propriedade CategoriaId (chave estrangeira) e Categoria (navegação) indicam que um produto precisa estar associado a uma categoria para ser completo. Sem Categoria, o produto pode existir, mas sua categorização fica inválida ou limitada (ex.: em consultas ou validações).

Produto não depende diretamente de ItemPedido, mas a propriedade Itens (coleção de ItemPedido) sugere uma relação inversa: ItemPedido depende de Produto (provavelmente via uma propriedade como ProdutoId em ItemPedido). Aqui, Produto "sabe" sobre ItemPedido, mas a dependência primária é de ItemPedido para Produto.

Evidência: Em um ORM como Entity Framework, isso cria uma dependência estrutural. Produto não pode ser totalmente funcional sem resolver a relação com Categoria.

using System.Collections.Generic;
public class Categoria
{
    public int Id { get; set; }
    public string Nome { get; set; }
    public ICollection<Produto> Produtos { get; set; } = new List<Produto>();

Aqui, Categoria não depende diretamente de nenhuma outra classe no código fornecido. Ela pode existir de forma independente (ex.: uma categoria sem produtos associados).

No entanto, a propriedade Produtos (coleção de Produto) indica uma relação inversa: Produto depende de Categoria (via CategoriaId e Categoria em Produto). Aqui, Categoria "sabe" sobre Produto, mas a dependência primária é de Produto para Categoria.

Evidência: Em um ORM como Entity Framework, isso cria uma dependência estrutural indireta. Categoria não precisa de Produto para ser criada, mas operações como listagem de produtos por categoria dependem dessa relação.

using System.Collections.Generic;
public class Cliente
{
    public int Id { get; set; }
    public string Nome { get; set; }
    public ICollection<Endereco> Enderecos { get; set; } = new List<Endereco>();

e :

public class Endereco
{
   public int Id { get; set; }
    public string Rua { get; set; }
    public string Cidade { get; set; }
    public string Cep { get; set; }
   public int ClienteId { get; set; }
   public Cliente Cliente { get; set; }
}

Na definição destas duas entidades,  Cliente não depende diretamente de Endereco para existir (pode ser criado sem endereços). No entanto, a coleção Enderecos indica uma relação inversa: Endereco depende de Cliente (via ClienteId e Cliente).

Já, Endereco depende fortemente de Cliente: As propriedades ClienteId (chave estrangeira) e Cliente (navegação) tornam Endereco incompleto sem um cliente associado. Sem Cliente, um endereço não faz sentido no contexto (ex.: não pode ser validado ou associado a pedidos).

acoplamento moderado a alto entre elas: Mudanças em Cliente (ex.: deletar um cliente) podem cascatear para Endereco (ex.: endereços órfãos ou deleções em cascata). Da mesma forma, adicionar/remover endereços afeta a coleção em Cliente.

Evidência: Em um ORM como Entity Framework, isso cria uma dependência estrutural. Endereco não pode ser persistido ou consultado isoladamente sem resolver a relação com Cliente.

Resumindo, o que este código entrega:

✔ Relacionamentos explícitos via chaves estrangeiras
✔ Navegação bidirecional clássica
✔ Entidades anêmicas
✔ Modelo fortemente acoplado
✔ Total mutável
✔ Regras fora do domínio

Ele é perfeito para demonstrar o CRUD tradicional “bem feito” tecnicamente, mas frágil conceitualmente.

O que isso causa no futuro ?

Mudanças pequenas geram efeitos colaterais grandes.
Evoluir o modelo vira risco. O sistema fica “frágil”
O alto acoplamento não explode agora. Ele cobra juros depois.

A seguir veremos os principais conceitos usados na abordagem do DDD.

E estamos conversados...  

"Como, pois, recebestes o Senhor Jesus Cristo, assim também andai nele,Enraizados e edificados nele, e confirmados na fé, assim como fostes ensinados, nela abundando em ação de graças"
Colossenses 2:6,7

Referências:


José Carlos Macoratti