ADR-022: Convenção de sinalização de erros (Result, nullable, exceptions)
Status: accepted Data: 2026-07-03 Autor: Solutions Architect (revisão Clean Architecture / Uncle Bob) Spec-Id: — LikeC4 view: —
Contexto
A revisão de Clean Architecture (docs/analysis/REVISAO_CLEAN_ARCHITECTURE_UNCLE_BOB.md) apontou
inconsistência na forma de sinalizar falha na camada de aplicação: coexistem três estilos —
Result<T>/OperationResult, retorno null (via T?) e exceções de domínio. A regra do projeto
(.cursorrules, backend-standards) diz "NUNCA retorne null de métodos públicos".
Existem hoje ~35 métodos de consulta públicos em Arah.Application/Services/** com assinatura
Task<T?> onde null significa "não encontrado" (ex.: GetByIdAsync, GetByKeyAsync,
GetActiveConfigAsync). Convertê-los em massa para Result<T> alteraria o contrato de dezenas de
métodos, quebraria todos os call sites (controllers, outros services e testes) e representaria
churn de alto risco com benefício real baixo — exatamente o tipo de mudança que o próprio critério
Uncle Bob classifica como needless complexity / viscosity quando forçada sem necessidade.
Decisão
Formalizar a convenção (em vez de reescrever), tornando o smell de inconsistência uma regra explícita:
Result<T>/OperationResult— obrigatório para comandos e operações que podem falhar com um motivo de negócio que o chamador precisa distinguir (criar, atualizar, validar, aprovar, processar pagamento). O motivo da falha faz parte do contrato.T?(nullable reference type) — permitido para consultas simples "get-or-null", onde a ausência (null) é o único desfecho alternativo e não carrega motivo.T?é o idioma moderno do C# (nullable reference types habilitado) e comunica a ausência no próprio tipo. Isto reinterpreta a regra "nunca retorne null": ela vale para métodos ondenullseria ambíguo (erro vs vazio vs não-inicializado), não para consultas cujoT?é semanticamente "não encontrado".- Exceções de domínio (
ValidationException,NotFoundException,ConflictException,DomainException) — reservadas para o excepcional e para o guard de invariantes; mapeadas a status HTTP noUseArahExceptionHandler.
Regra prática: se o chamador precisa saber por que falhou → Result<T>. Se só precisa saber
se existe → T?. Se é uma violação de invariante/estado impossível → exceção.
Consequências
Positivas
- Elimina a ambiguidade "qual estilo usar" sem churn de risco em 35+ arquivos e seus testes.
- Preserva comportamento e contratos atuais (nenhuma quebra).
- Alinha com nullable reference types (o compilador já força o tratamento de
T?).
Negativas / trade-offs
- Mantém dois estilos de "não encontrado" no código (Result vs null), agora por regra e não por acaso.
- Exige revisão em PR para garantir que novos comandos usem
Result<T>(nãonull).
Alternativas consideradas
- Converter tudo para
Result<T>(rejeitada): alto risco, quebra call sites/testes, adiciona indireção a consultas triviais — needless complexity. - Introduzir
Option<T>/Maybe (rejeitada por ora): mais um tipo/dependência conceitual sem ganho sobreT?do C# moderno; reavaliar só se surgir necessidade de encadeamento monádico amplo.
Diagrama
—