DDD -
Mantenha suas entidades simples
![]() |
Manter as entidades simples vai ajudar o seu código a ficar mais limpo vai simplificar sua arquitetura e tornar o seu código mais fácil de manter |
As Entidades infladas (ou bloated entities) são classes de domínio que acumularam lógica excessiva além de sua responsabilidade principal de proteger suas próprias regras internas (invariants).
Elas são um sintoma de design fraco, pois acabam lidando com regras de negócio externas (como verificações de crédito) ou orquestrando a lógica de múltiplos agregados. Isso reduz drasticamente a testabilidade, aumenta o acoplamento do código e viola os princípios do Domain-Driven Design (DDD), tornando o sistema difícil de manter e escala
Consequências de ter entidade infladas:
- Entidades infladas prejudicam
sua base de código ao:
- Reduz a testabilidade: Você precisa de mocks para
serviços externos.
- Aumenta o acoplamento: Entidades dependem de agregados
não relacionados.
- Quebra princípios do DDD: Entidades devem apenas impor
suas próprias invariantes.
Além disso, misturar lógica externa em entidades torna o teste de unidade um pesadelo — evite isso isolando regras em serviços ou políticas.
A seguir veremos três motivos que afetam suas entidades:
1- Misturar
Lógica Externa: Entidades que chamam serviços externos (por exemplo,
verificações de crédito) aumentam o acoplamento.
2- Sobrecarga de
Invariantes: Entidades que lidam com regras entre agregados violam os princípios
do DDD.
3- Negligenciar a Testabilidade: Entidades infladas exigem mocks
complexos, tornando os testes frágeis.
Para ilustrar vamos analisar o seguinte cenário:
Você construiu uma entidade chamada Emprestimo que começou simples — até que as verificações de crédito e as regras de histórico de empréstimos a transformaram em uma confusão inflada e não testável.
Isso cria pesadelos de manutenção em projetos .NET, mas nem tudo esta perdido...
Veremos a seguir como Serviços de Domínio (Domain Services) e Objetos de Política (Policy Objects) podem simplificar seus modelos de domínio para testabilidade e escalabilidade.
Entidade Emprestimo
public class Emprestimo { public decimal Valor { get; private set; } public decimal TaxaDeJuros { get; private set; } public DateTime DataDeVencimento { get; private set; } public void Aprovar(decimal valorRequisitado, Cliente cliente) { if (valorRequisitado <= 0) throw new InvalidOperationException("Valor deve ser maior que zero"); if (!cliente.TemBomScoreDeCredito()) throw new InvalidOperationException("Score de crédito do cliente muito baixo"); if (cliente.TemEmprestimosPendentes()) throw new InvalidOperationException("Cliente tem empréstimos pendentes"); Valor = valorRequisitado; } } |
Os problemas desta entidade:
A entidade Emprestimo está validando regras externas ao seu próprio agregado (regra do Cliente).
A entidade Emprestimo não deveria depender da história de outro agregado (o próprio Cliente ou um histórico de Emprestimos).
A refatoração se justificada pois a entidade esta acoplada a agregados ou serviços externos.
Refatorando o código usando serviços de domínio
Os Serviços de Domínio (Domain Services) são classes stateless (sem estado) usadas em DDD para encapsular a lógica de negócio que não pertence naturalmente a nenhuma Entidade ou Objeto de Valor específico. Eles coordenam operações que envolvem múltiplos agregados ou realizam cálculos importantes no domínio. Seu papel é garantir que as regras de negócio complexas e transversais permaneçam no coração do domínio, mantendo as entidades focadas em suas próprias invariantes.
Sugestão de Melhoria para o Serviço de Domínio
Para
manter o Serviço de Domínio (Domain Service) o mais puro
possível, ele deve ser uma ferramenta de cálculo/avaliação de regras, e não uma
orquestrador de chamadas externas de infraestrutura.
Ideia Central: O
ServicoDeAprovacaoDeEmprestimo deve receber os dados
necessários já obtidos pela Camada de Aplicação e focar apenas na regra de
negócio:
Camada de Aplicação (ServicoDeAplicacaoDeEmprestimo):
Responsável por chamar ICreditService para obter o score e o
IRepository para obter o histórico de empréstimos.
Domínio (IServicoDeAprovacaoDeEmprestimo):
Responsável por calcular a aprovação com base nos dados fornecidos.
// 1. O Contrato do Serviço de Domínio é Puro (Não recebe dependências de infra) public interface IServicoDeAprovacaoDeEmprestimo { // Recebe apenas dados já obtidos pela Camada de Aplicação bool PodeAprovar(decimal valorRequisitado, bool temBomCredito, bool temEmprestimosPendentes); } public class ServicoDeAprovacaoDeEmprestimo : IServicoDeAprovacaoDeEmprestimo { // Apenas lógica pura do domínio public bool PodeAprovar(decimal valorRequisitado, bool temBomCredito, bool temEmprestimosPendentes) { if (valorRequisitado <= 0) return false; if (!temBomCredito) return false; if (temEmprestimosPendentes) return false; return true; } } |
Assim podemos inicialmente simplificar a entidade:
public class Emprestimo { public decimal Valor { get; private set; } public bool EstaAprovado { get; private set; } internal void Aprovar(decimal valorRequisitado) { // Apenas muta o estado interno, mantendo a entidade pura Valor = valorRequisitado; EstaAprovado = true; } } |
Este código não esta completo e iremos ajustá-lo adiante mas mostra como a entidade já ficou mais simples.
Como Emprestimo Usa ServicoDeAprovacaoDeEmprestimo (via Camada de Aplicação)
public class ServicoDeAplicacaoDeEmprestimo { private readonly IServicoDeAprovacaoDeEmprestimo _servicoDeAprovacao; public ServicoDeAplicacaoDeEmprestimo(IServicoDeAprovacaoDeEmprestimo servicoDeAprovacao) { _servicoDeAprovacao = servicoDeAprovacao; } public Emprestimo RequisitarEmprestimo(Cliente cliente, decimal valorRequisitado) { if (!_servicoDeAprovacao.PodeAprovarEmprestimo(cliente, valorRequisitado)) throw new InvalidOperationException("Empréstimo não pode ser aprovado."); var emprestimo = new Emprestimo(); emprestimo.Aprovar(valorRequisitado); return emprestimo; } } |
Elevando o Nível com Objetos de Política
Os objetos de Política (Policy Objects) são um
padrão de design em DDD (Domain-Driven Design) usado para lidar com regras de
negócio complexas, voláteis ou que envolvem múltiplas condições.
Eles
servem como uma alternativa ou um complemento aos Serviços de Domínio quando a
lógica principal é uma decisão de sim/não baseada em um conjunto de regras que
podem ser alteradas ou combinadas.
Objetos de Política (Policy
Objects) são mais adequados quando as regras são muitas, combináveis e
propensas a mudanças.
Construindo objetos de política para o nosso exemplo:
public interface IPoliciaDeAprovacaoDeEmprestimo { bool EstaSatisfeitaPor(Cliente cliente, decimal valorRequisitado); } public class PoliticaDeValorPositivo : IPoliciaDeAprovacaoDeEmprestimo { public bool EstaSatisfeitaPor(Cliente cliente, decimal valorRequisitado) => valorRequisitado > 0; } public class PoliticaDeBomCredito : IPoliciaDeAprovacaoDeEmprestimo { private readonly IServicoDeCredito _servicoDeCredito; public PoliticaDeBomCredito(IServicoDeCredito servicoDeCredito)
=> _servicoDeCredito = servicoDeCredito;
public bool EstaSatisfeitaPor(Cliente cliente, decimal valorRequisitado) => _servicoDeCredito.TemBomCredito(cliente); } public class PoliticaDeSemEmprestimosPendentes : IPoliciaDeAprovacaoDeEmprestimo { public bool EstaSatisfeitaPor(Cliente cliente, decimal valorRequisitado) => !cliente.TemEmprestimosPendentes(); } |
Combinando Políticas com Composição:
public class PoliticaDeAprovacaoDeEmprestimoComposta : IPoliciaDeAprovacaoDeEmprestimo { private readonly IEnumerable<IPoliciaDeAprovacaoDeEmprestimo> _politicas; public PoliticaDeAprovacaoDeEmprestimoComposta(IEnumerable<IPoliciaDeAprovacaoDeEmprestimo> politicas) { _politicas = politicas; } public bool EstaSatisfeitaPor(Cliente cliente, decimal valorRequisitado) => _politicas.All(p => p.EstaSatisfeitaPor(cliente, valorRequisitado)); } |
Uso:
var politicas = new List<IPoliciaDeAprovacaoDeEmprestimo> { new PoliticaDeValorPositivo(), new PoliticaDeBomCredito(servicoDeCredito), new PoliticaDeSemEmprestimosPendentes() }; var politicaDeAprovacao = new PoliticaDeAprovacaoDeEmprestimoComposta(politicas); if (politicaDeAprovacao.EstaSatisfeitaPor(cliente, valorRequisitado)) { emprestimo.Aprovar(valorRequisitado); } |
Use Objetos de Política quando as regras mudarem com frequência, pois são mais fáceis de trocar ou estender sem modificar a lógica central.
O uso de Policy Objects é uma abordagem de alto nível para lidar com regras complexas e mutáveis, e a implementação apresentada está tecnicamente correta para este propósito.
Código completo da entidade Emprestimo:
public class Emprestimo { // Apenas propriedades internas (estado do próprio Empréstimo) public Guid Id { get; private set; } public Guid ClienteId { get; private set; } public decimal ValorRequisitado { get; private set; } public StatusEmprestimo Status { get; private set; } // Construtor: Força a criação inicial com um estado válido public Emprestimo(Guid clienteId, decimal valor) { if (valor <= 0) throw new ArgumentException("O valor do empréstimo deve ser positivo."); this.Id = Guid.NewGuid(); this.ClienteId = clienteId; this.ValorRequisitado = valor; this.Status = StatusEmprestimo.AguardandoAprovacao; } // Comportamento (Método) Focado no Domínio Interno public void Aprovar() { if (this.Status != StatusEmprestimo.AguardandoAprovacao) { throw new InvalidOperationException("Não é possível aprovar um empréstimo que não está aguardando."); } this.Status = StatusEmprestimo.Aprovado; } public void Rejeitar() { if (this.Status != StatusEmprestimo.AguardandoAprovacao) { throw new InvalidOperationException("Não é possível rejeitar um empréstimo que não está aguardando."); } this.Status = StatusEmprestimo.Rejeitado; } } |
O que mudou e por quê?
A grande mudança é a remoção
total de chamadas a serviços externos (IServicoCredito ou IRepository) dentro da
entidade.
| Antes (Entidade Inflada) | Depois (Entidade Pura) |
Tinha o método
TentarAprovar(ICreditService service) |
Agora tem apenas
Aprovar()
e Rejeitar() |
| Sabia sobre score de crédito e outros empréstimos. | Não sabe de regras externas; apenas muda seu próprio status. |
| Quebrava a regra do Agregado: acoplada a outros agregados e à infraestrutura. | Protege suas Invariantes (Valor > 0).
A decisão de aprovação é feita externamente (na camada de Aplicação/Serviço de Domínio). |
E estamos conversados...
"Bendito o Deus e Pai de nosso Senhor Jesus Cristo, o qual nos abençoou com
todas as bênçãos espirituais nos lugares celestiais em Cristo;"
Efésios 1:3
Referências:
NET - Unit of Work - Padrão Unidade de ...