04 - Modelo Rico vs Modelo Anêmico
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.