Decisões

ADR 2 — Notificação acumulada em vez de exceção

decidido em 2023status em vigor, publicado como open sourcedomínio Domínio e bibliotecasligações 8

A pergunta de projeto

Regra de negócio violada é o caso mais comum de “deu errado” num sistema corporativo: valor de devolução maior que o disponível, documento duplicado, canal sem fluxo configurado, voucher já usado. Não é falha do programa, é resultado previsto.

A pergunta é: como a regra reclama? A resposta se propaga por tudo — assinatura de método, tratamento no controller, formato da resposta HTTP, experiência de quem preenche o formulário. É barata de decidir uma vez e cara de mudar depois.

Por que exceção não serve

Exceção é o caminho idiomático do .NET e era o que a maior parte do código já fazia, então essa era a opção que não exigia justificativa nenhuma. Descartei por dois motivos, e o segundo demorou mais para eu enxergar.

O primeiro é comportamental: exceção interrompe no primeiro problema, e a tela quer mostrar os cinco campos errados de uma vez. Devolver um erro por vez transforma o preenchimento num jogo de tentativa e erro.

O segundo é conceitual, e é o que realmente decide. Exceção comunica “aconteceu algo que o programa não previu”, e regra de negócio violada é o contrário: o programa previu e tem opinião sobre isso. Usar exceção para as duas coisas apaga a distinção entre “o cliente errou o CPF” e “o banco caiu”, que merecem tratamento, log e alerta completamente diferentes. Quando essa distinção some do tipo, ela some também do monitoramento.

O Result foi o mais difícil de descartar

Um tipo que carrega sucesso ou erro, com o compilador forçando o tratamento, é tecnicamente superior no ponto exato onde a minha escolha é fraca: ele não deixa esquecer.

O que o inviabilizou não foi mérito, foi contexto. Result contamina toda assinatura no caminho, e num código onde o idioma dominante é .NET convencional isso significa ou converter tudo, ou conviver com duas convenções — que é pior que qualquer uma das duas isoladamente. Some a isso que Result naturalmente carrega um erro; acumular vários exige envolvê-lo numa coleção e reintroduzir a complexidade que ele existia para evitar.

A decisão

O domínio registra o que está errado e continua avaliando. Ao final, quem chamou pergunta se é válido e recebe a lista completa. Exceção fica reservada para o que é genuinamente excepcional: dependência fora, integração indisponível, estado impossível.

A fraqueza que eu aceitei

Nada obriga o chamador a verificar. Esquecer o if (!valido) produz sucesso silencioso, que é o pior tipo de bug: não falha, só faz a coisa errada. Result não teria esse problema, e eu sabia disso ao decidir.

Aceitei porque o custo é mitigável por disciplina e por ferramenta, enquanto o custo de duas convenções convivendo no mesmo código não é.

Como envelheceu

Melhor do que eu esperava, e a mitigação veio de onde eu não previa. Quando o mediator ganhou pipeline de comportamento (PMQ.Mediator), a verificação passou a acontecer num estágio antes do handler: o caso de uso nem executa se as notificações não estiverem limpas.

Isso resolveu estruturalmente a fraqueza que eu tinha aceitado como custo permanente. Não por planejamento — as duas decisões foram tomadas em momentos diferentes e simplesmente se encaixaram.

A confirmação externa veio depois: PMQ.Notifications é o mais baixado dos cinco pacotes que publiquei, com mais downloads que qualquer um dos outros. É o problema mais universal da suíte.

O que eu faria diferente

Faria o caso não verificado falhar alto em desenvolvimento: um objeto de notificação que, ao ser descartado sem consulta, reclama em tempo de execução fora de produção. Recuperaria boa parte da garantia que eu abri mão, sem o custo de propagação de tipo. É pequeno, é óbvio em retrospecto, e eu não fiz.

Ver também

PMQ.Notifications · Suíte PMQ · Modelagem de domínio, DDD e CQRS

Pablo Mickael Quevedo Senior Software Engineer · autor principal de 32 sistemas · Novo Hamburgo, RS

8 notas ligadas a esta

A vizinhança desta estrela no céu do portfólio.

Explorar a galáxia →