PMQ.Notifications
1.550 downloads · o mais adotado da suíte · repositório
Como uma regra de negócio reclama, num sistema .NET, acumulando o que está errado em vez de lançar exceção no primeiro problema.
O problema
“Valor da devolução maior que o disponível” é um resultado previsto: o programa antecipou que isso pode acontecer e tem opinião sobre. Tratar com exceção tem dois custos: ela interrompe no primeiro problema, quando a tela quer mostrar os cinco campos errados de uma vez; e apaga a diferença entre “o cliente errou o CPF” e “o banco caiu”, que merecem log, alerta e tratamento completamente diferentes.
A escolha está escrita no próprio código:
/// Accumulates rule violations instead of throwing, so that a broken business rule stays an
/// expected outcome rather than exceptional control flow. Callers check IsValid and decide
/// what to do with ValidationResult.
public abstract class Validatable
{
[JsonIgnore] public ValidationResult ValidationResult { get; } = new();
[JsonIgnore] public bool IsValid => ValidationResult.IsValid;
[JsonIgnore] public bool IsInvalid => !IsValid;
}
Três decisões que eu defendo aqui
Categoria é objeto de valor, não enum. Um enum fecha o conjunto: quem consome o pacote não
consegue acrescentar uma categoria sem alterar a biblioteca. Aqui NotificationType é uma classe
selada com instâncias estáticas: Validation, BusinessRule, NotFound, InconsistentState,
AccessDenied, mais um Custom(valor) para o caso que eu não previ:
public sealed class NotificationType : IEquatable<NotificationType>
{
public static readonly NotificationType BusinessRule = new("BUSINESSRULE");
public static readonly NotificationType NotFound = new("NOTFOUND");
public static NotificationType Custom(string value) => new(value);
}
Custa mais código que um enum e paga na primeira vez que alguém precisa de uma categoria que a
biblioteca não tem. Igualdade por valor, com == e GetHashCode, para o tipo se comportar como
dado e não como referência.
[JsonIgnore] no estado de validação. Detalhe pequeno com consequência real: sem isso, o objeto
serializado numa resposta de API leva junto a lista de erros internos. É o tipo de vazamento que
ninguém percebe até aparecer no payload de produção.
Construído sobre o ValidationResult do FluentValidation, e não sobre um tipo próprio. Perde-se
independência e ganha-se interoperabilidade com o validador que praticamente todo projeto .NET já
usa, porque quem já escreve validador não aprende um segundo vocabulário.
A fraqueza que eu aceito
Nada obriga o chamador a verificar IsValid. Esquecer produz sucesso silencioso, que é o pior tipo
de defeito. Um tipo Result teria o compilador como garantia, mas contamina toda assinatura no
caminho e obrigaria a conviver com duas convenções no mesmo código, o que é pior que qualquer uma
das duas.
Na prática a fraqueza acabou resolvida por outro lado: quando o PMQ.Mediator ganhou pipeline, a verificação passou a acontecer num estágio antes do handler, e o caso de uso nem executa se as notificações não estiverem limpas.
Por que é o mais baixado da suíte
Porque é o problema mais universal. Todo sistema com regra de negócio precisa decidir como a regra reclama, e a resposta idiomática do .NET, que é lançar exceção, é a errada para a maioria dos casos.