Pular para o conteúdo

Domain Model

“An object model of the domain that incorporates both behavior and data.”

Ao contrário do Transaction Script, a lógica não fica em procedimentos externos, mas dentro dos objetos de domínio. Cada objeto conhece suas regras e as impõe; o estado inválido deve ser impossível de representar.

Domínios complexos têm regras que se entrelaçam e mudam com frequência. Espalhá-las por procedimentos gera duplicação e inconsistência. O Domain Model localiza cada regra junto aos dados que ela governa, reduzindo duplicação e tornando as invariantes explícitas.

  • Regras de negócio complexas, ricas e mutáveis.
  • Domínio central do negócio (core domain) que merece investimento.
  • Quando invariantes precisam ser garantidas de forma consistente em todos os fluxos.
Vantagem Custo
Invariantes locais e explícitas Custo inicial alto
Baixa duplicação de regra Requer mapeamento O/R (impedância objeto-relacional)
Escala com a complexidade Curva de aprendizado maior
Alta testabilidade (POJOs) Overkill para CRUDs

Para lógica rasa/CRUD: o Transaction Script entrega o mesmo resultado com muito menos cerimônia. Aplicar Domain Model a um domínio trivial é sobre-engenharia.

O Domain Model é a fundação sobre a qual o Domain-Driven Design tático constrói vocabulário disciplinado:

classDiagram
    class Conta {
        -BigDecimal saldo
        -Status status
        +debitar(valor)
        +creditar(valor)
    }
    class Movimento {
        -Tipo tipo
        -BigDecimal valor
    }
    Conta "1" --> "*" Movimento : registra

O Anemic Domain Model é o anti-padrão em que os objetos têm só getters/setters e nenhum comportamento — toda a regra migra para serviços. Isso é um code smell: mantém a aparência de OO mas com a estrutura de um Transaction Script, sem as vantagens de nenhum dos dois. Fowler o classifica explicitamente como anti-padrão.

@Entity
public class Conta {
@Id
private Long id;
private BigDecimal saldo;
@Enumerated(EnumType.STRING)
private Status status;
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "conta_id")
private List<Movimento> movimentos = new ArrayList<>();
protected Conta() {}
public Conta(Long id) {
this.id = id;
this.saldo = BigDecimal.ZERO;
this.status = Status.ATIVA;
}
public void debitar(BigDecimal valor) {
exigirValorPositivo(valor);
exigirAtiva();
if (saldo.compareTo(valor) < 0) {
throw new SaldoInsuficienteException(id, saldo, valor);
}
this.saldo = this.saldo.subtract(valor);
this.movimentos.add(new Movimento(Tipo.DEBITO, valor));
}
public void creditar(BigDecimal valor) {
exigirValorPositivo(valor);
exigirAtiva();
this.saldo = this.saldo.add(valor);
this.movimentos.add(new Movimento(Tipo.CREDITO, valor));
}
private void exigirAtiva() {
if (status != Status.ATIVA) {
throw new ContaInativaException(id);
}
}
private void exigirValorPositivo(BigDecimal valor) {
if (valor == null || valor.signum() <= 0) {
throw new IllegalArgumentException("Valor deve ser positivo");
}
}
public BigDecimal getSaldo() {
return saldo;
}
}
@Service
public class TransferenciaService {
private final ContaRepository contas;
public TransferenciaService(ContaRepository contas) {
this.contas = contas;
}
@Transactional
public void transferir(Long origemId, Long destinoId, BigDecimal valor) {
Conta origem = contas.findById(origemId).orElseThrow();
Conta destino = contas.findById(destinoId).orElseThrow();
origem.debitar(valor);
destino.creditar(valor);
contas.save(origem);
contas.save(destino);
}
}

A regra “saldo não pode ficar negativo” e “conta precisa estar ativa” vivem dentro de Conta, garantidas em qualquer fluxo. O serviço apenas orquestra (Service Layer), sem conter regra de domínio.

  • Modelo anêmico: entidades só com getter/setter; regra toda no serviço.
  • Vazamento de invariante: permitir setSaldo público, contornando as regras de debitar/creditar.
  • Agregados grandes demais: fronteiras de consistência inchadas geram contenção transacional.
  • Persistência dirigindo o modelo: deixar a modelagem de tabelas ditar as classes de domínio, corrompendo o modelo rico.
  • Confundir com Service Layer: o serviço orquestra; o comportamento mora nas entidades.

1. O que distingue um Domain Model de um Transaction Script?

Resposta

O Domain Model coloca comportamento e dados juntos nos objetos, que protegem suas invariantes; o Transaction Script mantém dados passivos e a lógica em procedimentos externos.

2. O que é um modelo anêmico e por que é problemático?

Resposta

É um “Domain Model” com objetos só de dados (getters/setters) e regra nos serviços. Tem o custo estrutural do OO sem os benefícios: nem a simplicidade do Transaction Script nem o encapsulamento do Domain Model. Fowler o trata como anti-padrão.

3. Como o Domain Model se relaciona com o DDD tático?

Resposta

É sua base: Entidades, Value Objects e Agregados são técnicas para construir um Domain Model rico com identidade, imutabilidade e fronteiras de consistência bem definidas, guiado pela linguagem ubíqua.

4. Quando o Domain Model é sobre-engenharia?

Resposta

Em domínios de lógica rasa (CRUDs), onde o custo de mapeamento O/R e a curva de aprendizado não se pagam — ali o Transaction Script é mais adequado.