Home Comunidade Eventos YouTube Networks Desafios // produtos Academy News CTI | CVE Contato

04 - Modelo Rico vs Modelo Anêmico

#ddd#encapsulamento#invariantes#modelo-rico
video explicativo
Video explicativoEspaco reservado para o video desta anotação — adicione video: https://youtu.be/... no frontmatter da nota.

Modelo Rico vs Modelo Anêmico

O modelo anêmico é aquele em que as classes de domínio carregam apenas dados, com atributos
públicos ou getters e setters para tudo, enquanto toda a lógica de negócio vive em classes de
serviço externas. O modelo rico posiciona o comportamento junto dos dados que ele manipula, de modo
que a própria entidade protege suas regras e invariantes. A discussão é relevante em produção porque
o formato do modelo decide onde um bug pode nascer e quanto custa entender uma regra. Em um modelo
anêmico, para saber o que uma conta pode fazer, é preciso varrer todos os serviços que a recebem
como parâmetro. Em um modelo rico, abrir a classe da conta é suficiente, pois o contrato público
revela as operações válidas e os estados possíveis.

Origens do modelo anêmico

O antipadrão nasceu da combinação de três influências comuns no mercado. A cultura de JavaBeans e
das ferramentas de mapeamento objeto-relacional incentivou classes com construtor vazio e pares de
getter e setter, porque a infraestrutura de persistência se alimenta de propriedades. O mapeamento
table-to-class, que espelha tabelas de banco em classes, produziu objetos que representam linhas e
não conceitos de negócio. E a leitura apressada da arquitetura em camadas, que colocou "lógica de
negócio" em uma camada de serviços, levou equipes a esvaziar as entidades. Martin Fowler formalizou a
crítica ao apontar que objetos anêmicos violam o princípio do encapsulamento: dados e operações que
sobre eles existem ficam separados, e o objeto deixa de cumprir seu papel fundamental de guardar
invariantes. Não por acaso, ele classifica o modelo anêmico como antipadrão de design orientado a
objetos.

Custo real do modelo anêmico em produção

O primeiro custo é a dispersão de regras. Quando qualquer serviço pode chamar setSaldo(), a
invariante de que o saldo não pode ficar negativo mora em cada ponto que altera o campo, e uma
regra nova exige caçar todos esses pontos. O segundo custo é o estado inválido circulando: sem
construtor que valide, um objeto nasce quebrado e a falha aparece longe da origem, geralmente em
produção e com stack trace que aponta para quem leu e não para quem criou. O terceiro custo é
testabilidade: testar a regra exige montar o serviço inteiro, com todas as dependências, em vez de
testar a classe que concentra a regra. O quarto custo é social, e costuma ser o mais persistente: o
time para de discutir modelo de domínio, porque o modelo virou espelho do banco, e as decisões de
regra migram para serviços gigantes que ninguém consegue dividir sem refatorar.

O modelo rico na prática

O modelo rico aplica três mecanismos simples. Primeiro, o construtor cria objetos válidos, de modo
que nenhum caminho de código consegue instanciar um estado que viole regra. Segundo, mutações são
métodos com nomes de negócio, que validam a transição e atualizam apenas o que a operação permite.
Terceiro, não existem setters, então o único jeito de mudar o objeto é executar uma operação que o
próprio domínio define. O exemplo a seguir mostra a mesma regra de saque escrita nos dois estilos em
Java.

// Modelo anêmico: a regra vive fora do objeto e se repete em cada chamada
public class ContaAnemica {
    private BigDecimal saldo;              // exposto por getter e setter
    public BigDecimal getSaldo() { return saldo; }
    public void setSaldo(BigDecimal saldo) { this.saldo = saldo; }
}

public class ServicoDeSaqueAnemico {
    public void sacar(ContaAnemica conta, BigDecimal valor) {
        // invariante espalhada: cada serviço precisa lembrar dela
        if (valor.compareTo(conta.getSaldo()) > 0) {
            throw new IllegalStateException("saldo insuficiente");
        }
        conta.setSaldo(conta.getSaldo().subtract(valor));
    }
}

// Modelo rico: a regra nasce, vive e morre dentro da entidade
public final class Conta {
    private final ContaId id;
    private Dinheiro saldo;                // objeto de valor imutável

    private Conta(ContaId id, Dinheiro saldoInicial) {
        this.id = id;
        this.saldo = saldoInicial;
    }

    public static Conta abrir(ContaId id, Dinheiro saldoInicial) {
        if (saldoInicial.eNegativo()) throw new SaldoInicialInvalido(id);
        return new Conta(id, saldoInicial);
    }

    public void sacar(Dinheiro valor) {
        if (valor.eZeroOuNegativo()) throw new ValorDeSaqueInvalido(valor);
        if (!saldo.cobre(valor)) throw new SaldoInsuficiente(id, saldo, valor);
        this.saldo = saldo.subtrair(valor);
    }
}

Na versão rica, é impossível esquecer a verificação de saldo, porque não existe outro caminho para
alterar o campo. Um novo desenvolvedor pode usar a classe errada em qualquer outro aspecto, mas não
consegue criar uma conta negativa nem sacar sem passar pela regra.

Comparação de alternativas

Critério Modelo anêmico Modelo rico
Localização das regras Espalhadas em serviços externos Concentradas nas entidades e objetos de valor
Proteção de invariantes Depende da disciplina de quem escreve o serviço Garantida pelo encapsulamento e pelo construtor
Rastreabilidade de regra Busca textual em vários serviços Leitura da própria classe de domínio
Teste unitário Exige montar serviço e dependências Testa a classe isolada, sem mock de regra
Risco de estado inválido Alto, setters permitem qualquer valor Baixo, mutações passam por validação
Curva de entrada Menor para quem vem de CRUD Exige modelagem com especialistas do domínio

Quando o pragmatismo vence

Nem toda parte do sistema merece modelo rico, e insistir nisso vira burocracia. Telas de
administração, endpoints de consulta pura e subdomínios genéricos costumam ser CRUD honesto, onde
um modelo anêmico é mais barato e ninguém perde nada. Objetos de transferência de dados nas bordas,
como DTOs de entrada e saída de API, são anêmicos por design, porque sua função é exatamente
transportar dados entre processos. A fronteira recomendada é a do subdomínio: no core domain e nos
supporting com regras próprias, o modelo rico evita os custos descritos antes; em contextos
genéricos, o esforço de modelagem não se paga. O erro não é usar objetos anêmicos, é usá-los como
modelo de domínio onde existem regras que mudam o negócio.

Como migrar de um modelo anêmico existente

A conversão de uma base anêmica não acontece em uma refatoração, e tentar fazer de uma vez costuma
paralisar o time. O caminho que funciona começa pelas regras que mais mudam ou que mais geram
incidentes, porque ali o retorno aparece primeiro. Para cada regra escolhida, o serviço deixa de
atualizar campos e passa a chamar um método novo na entidade, que concentra a validação; os setters
usados apenas por essa regra são removidos no mesmo pull request. A persistência continua funcionando
porque frameworks modernos mapeiam atributos privados sem exigir setters públicos. Regras estáveis e
raramente tocadas ficam para depois, e algumas nunca migram, o que é aceitável. O critério de
parada também é claro: quando a entidade concentra suas invariantes e os serviços restantes apenas
orquestram casos de uso, a migração do contexto está completa.

Erros comuns e boas práticas

  • Confundir modelo rico com entidade que tem mil métodos. O objetivo é concentração das regras, e
    entidades grandes demais indicam agregado mal desenhado.
  • Migrar para modelo rico mantendo os setters "por segurança". Setter residual é a porta que anula
    todo o encapsulamento construído.
  • Colocar validação apenas na camada de apresentação ou na API. Validação de borda melhora a
    mensagem ao usuário, mas não substitui a invariante dentro do objeto.
  • Copiar o schema do banco para desenhar as entidades. O modelo deve nascer da linguagem do domínio,
    e o mapeamento de persistência é um detalhe de infraestrutura.
  • Transformar o serviço anêmico em serviço rico e manter tudo lá. A migração verdadeira move a regra
    para dentro do objeto, não troca o nome da classe que a contém.
  • Aplicar modelo rico em contexto genérico para seguir padrão. Racionalize o investimento: onde não
    há regra diferenciadora, a simplicidade vence.