|
Neste artigo vou continuar mostrando o porquê de fazer a validação no domínio da aplicação. |

Após mover a
validação básica para o domínio, a equipe sentiu-se confiante. Quantidades
inválidas? Bloqueadas. Preços negativos? Impossível. Mas então, um novo chamado
chegou.
“O cliente conseguiu aplicar o mesmo desconto duas vezes.”
Nenhuma exceção. Nenhum erro. Nenhum travamento. Apenas… comportamento errado.
Por que a validação básica não é suficiente
Aqui
está a verdade desconfortável: A maioria dos bugs de domínio não é sobre
dados inválidos — são sobre transições inválidas. Os dados pareciam bons. A
história é que estava errada.
Validação não é apenas sobre “isto é
válido?”. É sobre:
Isso pode acontecer agora?
Isso é permitido
após aquilo?
Isso já aconteceu?
Esta operação é idempotente?
Isso viola
um invariante?
É aqui que os Agregados entram na história.
No
DDD, a validação não acontece em todos os lugares. Ela acontece nos limites do
agregado.
Um agregado garante a consistência dentro do seu limite. Se uma
regra abrange múltiplas entidades — ela pertence à raiz do Agregado (Aggregate
Root).
O agregado Pedido (revisitado)
Vamos
expandir nosso agregado Pedido.
public sealed class Pedido { private readonly List<ItemPedido> _itens = new(); public PedidoId Id { get; } public StatusPedido Status { get; private set; } public IReadOnlyCollection<ItemPedido> Itens => _itens.AsReadOnly(); private Pedido(PedidoId id) { Id = id; Status = StatusPedido.Rascunho; } public static Pedido Criar() { return new Pedido(PedidoId.Novo()); } } |
Bem Simples. Mas agora vem o comportamento.
Regra: Você não pode modificar um pedido enviado
Esta não é uma regra de UI. Não é uma regra de API. É uma lei de negócio.
Vamos codificar a regra no comportamento
public void AdicionarItem(ProdutoId produtoId, Dinheiro preco, int quantidade) { GarantirRascunho(); if (quantidade <= 0) throw new ExcecaoDominio("A quantidade deve ser maior que zero."); _itens.Add(new ItemPedido(produtoId, preco, quantidade)); } private void GarantirRascunho() { if (Status != StatusPedido.Rascunho) throw new ExcecaoDominio("O pedido não pode ser modificado após o envio."); } |
Agora:
A regra é imposta.
Ela não
pode ser esquecida.
Ela não pode ser burlada.
Validação através de
transições de estado
Em vez de perguntar: “Este dado de entrada é
válido?”, o domínio pergunta: “Esta transição é permitida?”.
Enviando o pedido:
public void Enviar() { GarantirRascunho(); if (!_itens.Any()) throw new ExcecaoDominio("Não é possível enviar um pedido vazio."); Status = StatusPedido.Enviado;
}
|
Isso é validação. Mas não parece validação.
Parece comportamento. Esse é o ponto.
Compare isso com a abordagem
anêmica clássica: pedido.Status = StatusPedido.Enviado;
A validação acontece… em algum outro lugar. Talvez. Às vezes. É
assim que os invariantes morrem.
Objetos de Valor (Value
Objects): validação que você nunca repete
Entidades protegem o
estado. Objetos de Valor protegem o significado.
Dinheiro como um Objeto de Valor
public sealed record Dinheiro { public decimal Quantia { get; } private Dinheiro(decimal quantia) { Quantia = quantia; } public static Dinheiro Criar(decimal quantia) { if (quantia < 0) throw new ExcecaoDominio("Dinheiro não pode ser negativo."); return new Dinheiro(quantia); } public bool EhZero => Quantia == 0;
}
|
Agora: dinheiro negativo não pode existir, nenhum validador é necessário no serviço, e nenhuma verificação é repetida.
A seguir vamos comparar : Validação de Aplicação vs. Validação de Domínio
| Validação de Aplicação | Validação de Domínio |
| A requisição está bem formada? | Esta operação faz sentido? |
| Campos obrigatórios estão presentes? | Esta transição de estado é permitida? |
| O formato está correto? | Isso viola regras de negócio? |
Elas não são intercambiáveis. O FluentValidation, por exemplo, pertence à camada de aplicação/API para proteger as fronteiras externas.
Agora vejamos a última classe de bugs: Regrasa entre agregados
Exemplos:
Um
cliente só pode ter uma assinatura ativa.
Um pedido não pode ser enviado se a
conta estiver suspensa.
O erro: Forçar tudo dentro de
uma entidade
Injetar repositórios ou serviços dentro de entidades quebra o
encapsulamento e a testabilidade.
A solução: Serviços de
Domínio (Domain Services)
Um Serviço de Domínio existe quando uma regra não
pertence naturalmente a uma única entidade.
Exemplo:
public interface IPoliticaAssinatura { bool PodeCriarAssinatura(ClienteId clienteId); } public sealed class PoliticaAssinatura : IPoliticaAssinatura { private readonly IRepositorioAssinatura _repositorio; public PoliticaAssinatura(IRepositorioAssinatura repositorio) { _repositorio = repositorio; } public bool PodeCriarAssinatura(ClienteId clienteId) { return !_repositorio.PossuiAssinaturaAtiva(clienteId); } } |
O agregado não sabe sobre o repositório. A aplicação coordena:
public async Task<Resultado> CriarAssinatura(ClienteId clienteId) { if (!_politica.PodeCriarAssinatura(clienteId)) return Resultado.Falha("O cliente já possui uma assinatura ativa."); var assinatura = Assinatura.Criar(clienteId); await _repositorio.AdicionarAsync(assinatura); return Resultado.Sucesso();
}
|
Testando a validação de domínio
Se o seu domínio não é testado, ele não está protegido.
[Fact] public void Nao_deve_enviar_pedido_vazio() { var pedido = Pedido.Criar(); Action acao = () => pedido.Enviar(); acao.Should().Throw<ExcecaoDominio>() .WithMessage("Não é possível enviar um pedido vazio."); } |
Modelo Mental Final
A validação não
é uma camada. Validação é uma responsabilidade. E o domínio é o dono dela.
Entidades impõem invariantes.
Objetos de Valor impõem significado.
Serviços de Domínio lidam com regras entre agregados.
Quando a validação
vive no domínio, o sistema torna-se honesto e a refatoração tornam-se seguros
Resumo da Arquitetura de Validação
Para visualizar como as responsabilidades se distribuem agora que o
sistema está maduro, observe o fluxo de proteção:
A Fronteira
(API/Aplicação): O FluentValidation e o DataAnnotations
continuam sendo úteis, mas apenas para garantir que o que chega da internet não
é lixo (formato de e-mail, campos obrigatórios, tipos de dados).
O Coração (Domínio): As Entidades e Objetos de Valor garantem que, uma
vez que o dado entrou, ele respeite as leis do negócio. Se um Pedido diz que não
pode ser alterado após o envio, essa é uma verdade absoluta em todo o sistema.
A Orquestração (Serviços de Domínio): Quando a regra é
complexa demais para um único objeto, os Serviços de Domínio entram para manter
a pureza sem inflar as entidades.
O Saldo Final
A
longo prazo, essa abordagem traz três benefícios que pagam o investimento
inicial:
Código Autodocumentado: Ao ler o método Enviar(), qualquer
desenvolvedor entende as regras de negócio sem precisar procurar em manuais.
Testes que Duram: Seus testes unitários agora testam regras de negócio, e
não detalhes de implementação. Eles não quebram quando você troca o banco de
dados ou atualiza o framework.
Confiança: O sistema para de "deixar
passar" estados inconsistentes. Como diz o ditado: "Torne os estados ilegais
irrepresentáveis".
"A validação não é um obstáculo para o
desenvolvimento; é a garantia de que o sistema que você construiu é, de fato, o
sistema que o negócio precisa."
E estamos conversados
![]()
"Mas o Senhor Deus é a verdade; ele mesmo é o Deus vivo e o Rei eterno; ao seu
furor treme a terra, e as nações não podem suportar a sua indignação."
Jeremias 10:10
Referências: