Notification Pattern prevê uma forma permissiva que troca exceptions por notificações em um determinado contexto, no entanto embora a maioria das publicações a respeito falem em não lançar exceptions, veremos que isso não é bem assim.
Esse post é uma retaliação resposta ao post Não lance Exceptions em seu Domínio… Use Notifications! publicado pelo Wellington Nascimento. Para que você entenda o que falo aqui, ler o texto do Wellington é requisito fundamental, pois quase tudo que falo aqui se baseia na narrativa usada por ele.
No texto original Wellington diz:
O lançamento de exceptions no domínio traz alguns problemas:
Interrupção do fluxo de execução para cada inconsistência encontrada durante as verificações de regras de negócio;
São onerosas para o processador;
São deselegantes.
Esse tipo de afirmação demoniza as exceptions.
Afirmações assim fazem parecer boa ideia, e até quase lógica, abolirmos o uso de exceptions.
Essa é uma conclusão falaciosa que precisa ser melhor contextualizada, pois notification pattern sugere que você o aplique para validações.
Na prática, não abolimos exceptions, reduzimos a quantidade, movemos de lugar, mas de fato não deixamos de lançar exceptions.
Seguindo a proposta de aplicar notification pattern proposta pelo Wellington, vemos que ele deixa de lançar exceptions no preenchimento da entidade, o que produz uma nova possibilidade: A possibilidade de existir uma instância inválida, ou inconsistente.
Considerando o exemplo do post, mais especificamente da classe Customer, infelizmente Customer ainda é anêmica pois o exemplo dele parou na explicação sobre notification pattern e ele não abordou um cenário de negócio, onde pudéssemos ver métodos na classe Customer.
Assim, não podemos ver o que quero explicar pela perspectiva do código dele. Por isso tomarei a liberdade de copiar e escrever abaixo, para podermos discorrer a respeito.
public class Customer : Entity { public string Name { get; } public string Email { get; } public Customer(string name, string email) { Id = Guid.NewGuid(); Name = name; Email = email; Validate(this, new CustomerValidator()); } public void Store() { ... } public void Checkout(Cart cart, Payment payment, Address billingAddress, Address deliveryAddress) { ... } }
O exemplo dele não possui os métodos Store e Checkout, tomei liberdade de adicioná-los para dar vida ao meu ponto de vista.
A pergunta que faço é:
nos métodos Store e Checkout, o que fazer se o estado do Customer for inválido?
- Ignorar a ação?
- Retornar nulo?
- Usar notification pattern novamente?
- Lançar exceptions?
As opções 1, 2 e 3, para mim, estão categoricamente erradas.
Meus argumentos:
No método Store, vemos uma validação para garantir os requisitos da operação sejam atendidos. Nesse exemplo, eu determinei que a regra é que para o Customer ser persistido (salvo) é necessário que ele esteja em um estado válido.
Nesse contexto, não podemos questionar a regra, ela foi arbitrada como a maioria das regras de negócio das nossas aplicações.
O que temos e devemos debater aqui é sobre a implementação dessa regra.
No meu caso, Store seria o método que salva esse customer. Provavelmente acionando algo no repositório.
Eu particularmente não gosto desse modelo tão parecido com Active Record, mas resolvi entrar no clima do exemplo para dar vida à minha explanação.
O fato aqui é que o método Store(), não poderia executar suas tarefas, com uma instância de Customer inválida.
Na visão que eu defendo, o fluxo precisa ser interrompido, pois quem programou a classe que chama o método Store() ignorou essa necessidade.
Na minha visão, lançar essa exception, nesse caso, é OBRIGAÇÃO do método Store(), como no exemplo abaixo.
Seria assim:
public class Customer : Entity { public string Name { get; } public string Email { get; } public Customer(string name, string email) { Id = Guid.NewGuid(); Name = name; Email = email; Validate(this, new CustomerValidator()); } public void Store() { if(this.Invalid) throw new InvalidEntityException("Texto aqui"); ... } public void Checkout(Cart cart, Payment payment, Address billingAddress, Address deliveryAddress) { ... } }
Não cabe nesse post, a discussão se a exception deve ser genérica, específica, isso é outro aspecto a ser pensado e depende de como você está desenhando sua solução.
Se mesclado com notification pattern, eu tenderia a usar exceptions genéricas quase sempre.
Isso porque essas exceptions não precisam de tratamento específico, elas demandam apenas interromper o fluxo de processamento, pois algo não está consistente o suficiente para prosseguir.
Nesse caso não é sobre ser uma validação ou não, é sobre interromper o fluxo, pois há uma condição não permitida (que poderia ser evitada).
Marting Fowler deixa claro isso no post Replacing Throwing Exceptions with Notification in Validations.
No tópico When to use this refactoring ele escreve:
[en-us] I need to stress here, that I’m not advocating getting rid of exceptions throughout your code base. Exceptions are a very useful technique for handling exceptional behavior and getting it away from the main flow of logic. This refactoring is a good one to use only when the outcome signaled by the exception isn’t really exceptional, and thus should be handled through the main logic of the program. The example I’m looking at here, validation, is a common case of that.
[pt-br] Preciso enfatizar aqui que não estou defendendo a eliminação de exceções em toda a sua base de código. As exceções são uma técnica muito útil para lidar com um comportamento excepcional e afastá-lo do fluxo principal da lógica. Essa refatoração é boa para ser usada somente quando o resultado sinalizado pela exceção não for realmente excepcional e, portanto, deve ser tratado por meio da lógica principal do programa. O exemplo que estou vendo aqui, validação, é um caso comum disso.
Ele complementa com um trecho do livro Programador Pragmático (leitura recomendada).
Textos como esse do Fowler são cuidadosos ao deixar esse disclaimer para sua audiência. Ele se preocupa em considerar o que equivocadamente poderia ser conclusão óbvia. Conclusão esta que vemos em diversos posts sobre Notification Pattern, conclusões que discuti, por exemplo, em um vídeo do Software em Contexto.
O ponto é que exeptions são necessárias para cenários não previstos e também para cenários conhecidos e não suportados, como nesse exemplo, ao tentar persistir um Customer inválido.
Talvez no Checkout faça sentido também, o que é necessário avaliar é quais regras regem essa operação. E se a operação, imperativa, for demandada, com um requisito não atendido, devemos lançar uma exception.
O lançamento da exception é uma barreira, um bloqueio, que pode ser evitado, mas nunca suprimido
Ainda olhando para a nossa implementação de Customer com o método Store().
No primeiro momento, a exception lançada ao chamar o método Store() em uma instância inválida de Consumer, será lançada quando um programador consumir em seu código classe Customer e tentar persisti-la com um estado inválido.
Uma exception é uma forma do dev que criou uma classe, comunicar ao dev que vai consumir essa classe que:
- Algo catastrófico aconteceu.
- Algo não esperado, mas conhecido, aconteceu.
A função primária da exception é interromper o fluxo abruptamente, impedindo o progresso da execução. Entende-se que interromper abuptamente, notifica infraestrutura (em produção), notifica desenvolvedores (em fase de desenvolvimento) que algo, não suportado, aconteceu e portando há algum tipo de inconsistência ou bug.
É melhor uma exception que interrompa um fluxo, do que um pagamento errado, uma cobrança indevida, coisas comuns que acontecem diariamente em aplicações e serviços mal escritos.
A exception é um mecanismo de comunicação entre criador e consumidor de uma classe
Uma função secundária da exception é notificar um novo desenvolvedor, que ele não pode realizar uma determinada operação com base no estado da aplicação ou parte dela.
No nosso caso, não é possível persistir o customer inválido.
Talvez você possa achar essa ideia absurda. Mas eu tenho certeza que você sabe muito bem do que estou falando.
Toda primeira vez que você usa alguma library, alguma API, você toma diversas exceptions, até que aprenda a usar aquilo.
E não estamos falando de coisas absurdas, estamos falando de pura e simples validação de pré-condições.
Uma propriedade que faltou, um parâmetro incompatível com outro.
Há validações o tempo todo, lançando exceptions. E se olharmos pela ótica de Negócio, o negócio daquelas classes pode ser algo técnico como um acesso a banco, ou pode ser algo de alto nível como emitir um pagamento.
Não importa.
Você não precisa de Try/Catch
Um assunto que acabei esquecendo de abordar, foi o uso do try/catch.
Anos depois da versão inicial desse post, acabei esbarrando em um código que estava analisando. E vi, na minha frente, um milhão de try/catchs, para todo lado, em todo lugar.
Não é porque uma exception é lançada, que você precisa de um try/catch próximo à exception.
Em uma aplicação web, comum, o try/catch ficaria na controller apenas, ou abstraído em um handler de exceptions, global.
Se você tem uma classe que chama esse método Save() de Customer, ela não precisa ter um try/catch. Porque não queremos OMITIR a exceção.
Pelo contrário, a omissão da exceção com um try/catch faz com que ninguém tome ciência de que esse bug existe e portanto, teremos poucas ou nenhuma oportunidade de resolvê-lo.
Fobia de Exceptions – Erro 1 – Suprimindo exceptions
public class Blablabla { public Guid? DoSomething() { Guid? returnValue = null; try { var customer = new Customer("Luiz", "teste#teste.com"); customer.Store(); returnValue = customer.Id; } catch(){} return returnValue; } }
Eu chamo esse perfil de código de FOBIA de EXCEPTIONS. Os animais que produzem isso ficaram em cativeiro, “resolvendo” bugs em sistemas legados por anos.
O trauma de exceptions faz com que produzam esse tipo de bizarrice.
Ao encontrar um código assim, abrace quem fez e ofereça ajuda!
Brincadeiras à parte, o que temos aqui é a supressão da exception.
Ninguém ficará sabendo de que tem um erro no código.
O código só ficou mais complexo, e como a exception de fato pode estar sendo lançada, e como ninguém nunca vai ficar sabendo disso, então temos um caso em que se confirma a ideia de que exceptions causam lentidão.
Esse fluxo aqui é um fluxo lento, porque nunca teremos a oportunidade de corrigir o bug que faz a exception ser lançada.
A correção não é parar de lançar a exception, a correção é fazer o tratamento adequado para que ela nunca seja lançada.
Fobia de Exceptions – Erro 2 – Try/catch Mania
public class Blablabla { public Guid? DoSomething() { Guid? returnValue = null; try { var customer = new Customer("Luiz", "teste#teste.com"); customer.Store(); returnValue = customer.Id; } catch(exception Exception) { log.Error(exception); } return returnValue; } }
Esse aqui já não podemos fazer bullying.
O erro aqui é a existência desse try/catch omitindo a exception.
No exemplo passado, a abordagem de supressão foi criminosa, visto que nenhum log foi feito.
Aqui estamos diante de uma implementação com o mínimo de boa vontade.
A pessoa que comete esse tipo de erro, tem boas intenções, ela sabe que tem de logar, ela sabe que o que deve ser logado é a exception inteira, ou exception.ToString(), nunca apenas a propriedade Message da exception.
Tudo isso ela sabe, e portanto precisamos de milímetros para ajustar as coisas.
A forma correta é simples, é clean
public class Blablabla { public Guid DoSomething() { var customer = new Customer("Luiz", "teste#teste.com"); customer.Store(); return customer.Id; } }
A forma correta de lidar com a possível exception na chamada à Store() seria essa aqui.
Aqui estou presumindo que essa classe não é a controller.
Se fosse uma controller, resolveríamos o problema que causariam o lançamento da exception usando NOTIFICATION PATTERN, ou outra estratégia para rever o imput do usuário.
Com essa correção, nunca teríamos a exception sendo lançada em produção. Ou pelo menos não mais.
Entretanto não podemos de forma alguma remover o lançamento da exceção do método Store() da classe Customer.
Do ponto de vista da classe Customer, ela não sabe se existe validação prévia, ela sabe se vai ser chamada da sua aplicação web, de uma web api, de um consumer AMQP, de um serviço WCF, de um teste unitário ou de uma aplicação console ou até mesmo de um bot.
É por isso que ela faz o que precisa ser feito.
Para a classe Customer, não faz diferença quem está consumindo. A regra de negócio não muda dado o contexto. Customer inválido não é persistido e ponto final. Respeitando a regra de negócio.
Exemplos que ajudam a entender meu ponto
Trago aqui um exemplo simples, e perfeito para exemplificar uma abordagem, que na minha visão, é correta: System.IO.File.Delete
Nesse exemplo temos as exceptions possíveis, e são várias, quando tentamos excluir um arquivo. E se quiser identificar se o arquivo pode ser excluído, há métodos suficientes para isso, como o Exists entre outros. Mas ao realizar a operação, ao “Comandar” a execução do Delete, qualquer cenário adverso que não permita a realização da atividade irá retornar uma exceção. E, pra mim, está corretíssima essa abordagem.
Regra geral para exceptions
Minha regra para exceptions é: Toda operação deve ser capaz de validar se seus pré-requisitos são atendidos para a execução de sua atividade.
Requisitos não atendidos devem produzir exceções.
Isso não impede de você aplicar notification pattern para evitar que essas exceções sejam lançadas, se o fizer bem feito, essas exceções só serão lançadas nos seus testes unitários. Esse viés é parte da programação defensiva.
Será que faz sentido uma exception que só é lançada no teste unitário?
Sim, quanto maior o reaproveitamento, maiores são as chances de algum erro no código produzir novos cenários de inconsistência. Caso surja um novo consumidor de seu método/classe que não leve algo em conta ou simplesmente tenha sido programado errado, é importante não permitir inconsistência.
Evitar que as exceptions sejam lançadas não significa que você vá eliminá-las de sua base de código, pois elas servem como trava de bloqueio para evitar corrupção!
Mesclar notification pattern com exceptions é uma boa pedida. Para cada operação você divide validação e operação. No Checkout teríamos 2 métodos:
ValidateForCheckout()
Responsável pela validação, respondendo notificações.
Checkout()
Já o método Checkout() seria responsável por chamar o método ValidateForCheckout() para saber se suas pré-condições foram atendidas, e senão… lançar uma exception.
Já vi implementações de ValidateForCheckout() que recebe um parâmetro boolean informando se o resultado negativo lança ou não lança uma exception. Particularmente eu gosto, dessa abordagem também.
Conclusão
Não há discriminação a respeito do uso de Notification Pattern, é um padrão útil.
Mas dizer que não devem ser lançadas exceptions, é fantasioso.
Não importa se é negócio, não importa se é API, se é acesso a dados, se é AMQP, não importa o contexto.
Dizer que é deselegante, deslegante é você chamar um método que não faz o que deveria fazer e você só vai descobrir isso meses depois, com centenas, milhares ou até milhões de inconsistências em banco.
Diga: “não lance exceptions para validação” em vez de “não lance exceptions”. Tenha o cuidado de dizer quando usar exceptions.
Se realizadas as pré-validações nas camadas certas, é possível não lançar nenhuma exceção, mas isso nunca vai querer dizer que elas não devem existir no código.
Se você busca reaproveitar as classes que escreve, essas classes não podem depender de consumidores específicos. Ela não pode depender do comportamento prévio de quem ela sequer conhece. Assim, ela precisa garantir sua autonomia, fazendo o que precisa ser feito.
Não lançar exceptions pode lhe custar caro, principalmente se o nível de reaproveitamento de suas classes for alto.
O exemplo do System.IO.File.Delete é fantástico para tornar claro esse ponto de vista.
Notification Pattern não é problema, mas os posts que o defendem pecam em não deixar claro que as exceptions não morrem. Eles produzem a sensação de que exceptions deveriam ser abolidas e isso nem de longe é uma verdade.
#TretaFree
Vale lembrar que eu entrei em contato com o Wellington pedindo para citar o post dele.
Por falar em treta, já está na hora de falarmos de community rules, né!
Gostou desse post? Conta aí? Comente!
Espero que eu tenha conseguido ser claro na minha visão sobre o tema.
Update 03/07/2024 | Live Exceptions: tudo que você deveria saber sobre!
15:48 – Finalidade das Exceções em Produção
19:35 – Controle de Danos
25:46 – Exemplo “Contratação RH”
28:28 – Exceptions x Logs
30:52 – Exemplo “E-Commerce”
35:57 – “É importante saber regra de negócio?”
37:23 – “Existe Exception que não deve ser lançada?”
43:53 – Finalidade das Exceções em Desenvolvimento
45:09 – Exemplos de Exceptions da Microsoft
Artigo do Martin Fowler: https://martinfowler.com/articles/replaceThrowWithNotification.html
49:09 – Omissão de Exception
51:53 – Como pegar uma exception completa?
57:15 – Log hoje, log amanhã, log sempre!
1:07:26 – Respostas HTTP
1:13:38 – Diferença entre throw e throw ex
1:18:34 – Demo Using
1:23:33 – Tratamento = Caminho alternativo
1:25:56 – Exemplos com Polly
Repo: https://github.com/renatogroffe/ASPNETCore6-REST_API-RateLimitMiddleware-ClientId-CacheRedis_ContagemAcessos
1:50:10 – “Expor a exceção com Status Code correto x Falha de segurança?”
1:52:03 – Exemplo RateLimit
Repo: https://github.com/renatogroffe/DotNet6-Worker-Polly-RateLimit_Policy-APIM_SubscriptionKey_ConsumoAPIContagem
Neste evento ONLINE e GRATUITO do Canal .NET iremos abordar Exceptions, Notifications, boas práticas e princípios de manutenabilidade.
Tudo o que você precisa saber sobre Exceptions para manipulá-las corretamente em seus projetos! Palestrante: Luiz Carlos Faria (Microsoft MVP, MTAC) Ainda não segue o Canal .NET nas redes sociais? Faça sua inscrição então, para ficar por dentro de novidades sobre eventos, tecnologias Microsoft e outros conteúdos gratuitos: –
– Telegram: https://t.me/canaldotnet
– Facebook: / canaldotnet
– YouTube: / canaldotnet
Parabéns pelo post concordo totalmente com seu ponto de vista.
Um padrão comecei a estudar e aplicaria usando o Notification pattern junto com Exception seria de não deixar o objeto ser instanciado de forma inválida. No construtor ou usando um Fabrica chamaria a verificação “IsValid” e lançaria a InvalidArgumentException com as mensagens do ValidationResult
Obrigado Paulo!
O pattern defende algo diferente. Com as tuplas de retorno de métodos no C# seria possível mesclar validação e entidade de retorno (se não me engano GO trabalha assim, posso estar errado, eu não tenho certeza disso, mas acho que ouvi dizerem isso).
Muito boa essa discussão sobre validações, eu estava lendo alguns artigos sobre validação e tinha me deparado com várias abordagens, essa pra mim foi a que fez mais sentido e até me esclareceu alguns pontos que eu estava em dúvida. Valeu pelo post.
Muito obrigado pelo feedback Tiago, que bom que ajudou!
Bacana. Acho que complementou bem o post do Wellington. Tenho usado specifications com Exceptions em um pequeno projeto. Tá interessante de usar por enquanto. 🙂
Boa Leandro, embora tenha feito a firula de dizer que era uma retaliação o post dele é apenas um dos exemplos que pecam nesses pequeninos detalhes, mas que fazem diferença, pois muita gente se baseia em conteúdos assim para formar opinião. Acaba virando um mega telefone-sem-fio.
Muito bom seu ponto Gago, tinha lido posto do Wellington anteriormente e tinha achado muito interessante agora com seu complemento, mudou minha visão, estava com pensamento radical sobre as exceptions, seria bacana mais exemplos desses com notificação/exceção, obrigado!!
O ponto central que me leva a essa reflexão é que exceptions são fundamentais para bloquear processamento indevido e inconsistente. Que pode ser fruto de entrada do usuário ou simplesmente uma falha de implementação.
Adicionar Notification Pattern tem muito potencial para sanar problemas com performance e usabilidade, no entanto como não entra na alçada de bloquear processamento indevido e inconsistente, é instantaneamente óbvio que o padrão não suprime a necessidade de exceptions.
Acredito que o empenho de notification pattern é uma escolha dos times, dos projetos, diferente do uso de exceções, que está relacionado à confiabilidade de um kernel.
Bom dia, em primeiro lugar quero deixar claro que acho discussões sobre tecnologia e desenvolvimento extremamente valiosas e de grande aprendizado para todos nós, e em resposta à sua “retaliação” acho que tenho direito de trazer alguns pontos.
Lendo seu post fiquei com o sentimento de ser acusado de fazer algo muito errado, de estar ensinando conceitos errados para a comunidade de forma proposital, mas posso te garantir que tudo que escrevo é fruto de muito estudo e experiências que tive em projetos reais que tive o prazer de participar nos últimos anos e que aplico em meu dia a dia, e de forma alguma tentaria vender algo errado ou que não acredito para a comunidade.
Não estou tentando endemonizar o uso de Exceptions, elas são reconhecidamente recursos valiosos para travar/bloquear a execução de código e informar um comportamento inesperado, um erro na aplicação, e eu não sou ninguém para dizer que elas devem ser abolidas.
Se você leu meu artigo e entendeu isso, talvez eu tenha realmente me expressado mal (acho que faltou um disclaimer, como o que o Fowler fez), mas longe disso, eu como qualquer outro desenvolvedor uso exceptions em meus sistemas, tanto pessoais como em projetos comerciais em que atuo.
Provavelmente uma simples mensagem sua me informando sobre o ponto em questão me faria re-avaliar o que escrevi e fazer as devidas correções em meu artigo, pois como eu disse, de forma alguma quero endemonizar o uso das exceptions e não havia percebido que meu texto tinha essa “intenção”, e é justamente para melhorar os textos e corrigí-los se necessário que eu peço no final de cada artigo que me enviem críticas e sugestões. Vou fazer a correção, e ainda assim obrigado por avisar, mesmo que com uma “retaliação”. #communityrules
Wellington Nascimento,
1) Aprovei todos os comentários e aprovarei os demais, já que o texto pauta usa seu post como premissa de entendimento. Aprovarei todos os demais sem discriminação alguma.
2) Agradeço ter separado um tempinho para voltarmos ao assunto, isso é muito útil para a discussão.
Sobre ser acusado de estar ensinando conceitos errados para a comunidade:
A falta de alertas para quem lesse o post, tem potencial para concluir que sim, as afirmações que copiei só estão contextualizadas sob o aspecto de pertencerem a um domínio. Assim o trecho “O lançamento de exceptions no domínio traz alguns problemas:
Interrupção do fluxo de execução para cada inconsistência encontrada durante as verificações de regras de negócio;
São onerosas para o processador;
São deselegantes.”
Sem o devido disclaimer demoniza exceptions.
E sim, um disclaimer poderia ter resolvido facilmente quase toda a questão, eu concordo contigo.
Se uma mensagem em um comentário poderia produzir uma mudança, sim de fato, poderia. Se olhar bem o que escrevi em diversos momentos faço acenos claros e em momento algum eu lanço pedras no pattern.
A questão central é que seu texto se encaixa perfeitamente em um estereótipo de posts sobre Notification Pattern que levam o leitor rumo entendimento de que exceptions são ruins, onerosas e deselegantes independente do contexto. Embora não seja objetivo, é uma informação que se pode tirar, principalmente pela forma como algumas coisas foram escritas. São detalhes, mas produzem essa ideia.
communityrules é outro tópico (não tem a ver com esses textos, nem o meu, nem o seu) hahaha, isso é outro assunto que não tem nada a ver com esses posts.
Entendo seu ponto Luiz, e concordo com você que a falta de um disclaimer sobre as exceptions pode levar o leitor para um entendimento errôneo do que eu queria realmente dizer sobre usar exceptions no domínio, por isso disse que uma simples mensagem sua avisando sobre esse aspecto que eu não havia percebido, seria o suficiente para que eu melhorasse meu texto incluindo tal disclaimer e contextualizasse ainda mais o trecho específicio à que você se refere, evitando assim que futuros leitores caíssem nesse entendimento errado.
Se pela falta de um disclaimer meu texto caiu nesse estereótipo que você descreveu, sinto muito, falha minha, eu irei melhorar o texto e prestar mais atenção para que isso não volte à ocorrer no futuro, mas de forma alguma estou vendendo um conceito errado propositalmente.
Luiz, tirei um tempo no fim de semana e revi meu texto. Inclui o disclaimer que concordo que eu deveria ter colocado já na primeira versão do artigo, e também alterei outros pontos para ficar melhor contextualizado o que eu queria dizer. Se você tiver um tempo para refazer essa leitura e me dizer o que achou após as modificações eu ficaria grato.
Welligton,
Concordo com você em vários pontos. O Luiz faz várias afirmações baseadas nas opiniões dele. E são só isso, opiniões. Ele aponta assunto importantes como o fato de que uma Exception é uma das coisas mais caras (fato, não opinião) e deixa outros pontos óbvios de lado, por exemplo, que Exceptions são para exceções, ou seja, coisas que não foram previstas. Coisas previstas devem ser validadas e tratadas de forma mais inteligente, por exemplo, através do Notification Patterns (criado por gente muita mais experiente, capaz e inteligente que nós, com base em fato e não em opinioes).
Por experiência (e eu desenvolvo sistemas desde 1986) o Notification Patterns faz todo sentido e pode eliminar problemas de aplicações que “caem” por consumo excessivo de recursos (de novo, por experiência, não opinião).
Opiniões são apenas opiniões. Boas para conversas em bar, mas não para a área de Ciências Exatas, a qual pertencemos.
Marcio,
é curioso ver como que ao dizer O Luiz faz várias afirmações baseadas nas opiniões dele. E são só isso, opiniões. você acha, pressupõe que de alguma forma, remove a força do meu argumento. Não colega, essa escola de narrativa, não cola, sequer é bem vinda aqui. Todas as leituras que você tem feito são fruto primeiro de opiniões, depois talvez, de experimentação e análise.
Mas cuidado com para não se enforcar no próprio fio do teclado, porque quando você diz “Exceptions são para exceções, ou seja, coisas que não foram previstas” você gera um pressuposto absolutamente nocivo. Se levarmos essa afirmação ao pé da letra, NUNCA sob HIPÓTESE alguma, lançaríamos uma exception. Afinal, só é possível lançar uma exception nova, caso seja previsto o tipo de falha ou requisito, logo, pela sua lógica, se foi possível prever, não é uma exception.
Entende o tamanho do erro?
Como disse “Não há discriminação a respeito do uso de Notification Pattern, é um padrão útil. Mas dizer que não devem ser lançadas exceptions, não é legal. Dizer que é deselegante, também não é legal. Diga: “não lance exceptions para validação” em vez de “não lance exceptions”. Tenha o cuidado de dizer quando usar exceptions.” então era só ler.
Aquilo que em um extremo da aplicação é uma validação, necessariamente deveria ser uma exception em outro.
Mas na era do pós-verdade, não conseguimos ter essa discussão.
Ok, eu entendo.
Já se você prefere a literatura:
Código Limpo:
“Prefira exceções a retorno de códigos de erro” (p. 46)
e Cap 7 inteiro
Martin, Robert C.. Código Limpo (p. 9). Edição do Kindle.
Prosseguindo, em determinado momento você menciona que o uso do padrão deve ser contextualizado pois ele sugere que deve ser usado para validações, dando a entender que eu não mencionei o contexto onde as exceptions seriam substituídas por notificações. Bem, lendo meu próprio artigo acho que deixei bem claro que o pattern deve ser aplicado em validações de domínio.
“É muito comum encontrar nos sistemas corporativos o lançamento de Exceptions ao realizar validações de regras de negócio…”
“Para capturar as mensagens das validações de domínio podemos usar o pattern Notification…”
“Essa entidade expõe as mensagens de erro das validações através da propriedade ValidationResult da classe base…”
“…após instanciar a entidade Customer ela irá armazenar o resultado de suas validações na propriedade ValidationResult”
“Perceba que interrompemos o fluxo de execução apenas uma vez, após todas a validações terem sido feitas…”
Outro ponto, a questão de as exceptions serem “deselegantes” quando aplicadas em validações é pura e simplesmente uma opinião minha como desenvolvedor de software e eu realmente acho isso. Sobre os outros pontos, sim elas param abruptamente a execução de código mostrando apenas a primeira falha de validação encontrada, e elas realmente não são legais com o processador.
O ponto em questão é a forma de escrever.
Note, que no seu texto original você diz “O lançamento de exceptions no domínio traz alguns problemas:” … “São deselegantes.” e agora você diz “a questão de as exceptions serem deselegantes quando aplicadas em validações”.
Enquanto a primeira pode levar a um entendimento sobre o que as exceções são, o segundo traz o contexto. Por mais que o post fosse sobre notification, todo post é sobre um contexto determinado. E todo post traz consigo definições que são contextuais e definições que não são contextuais, é assim que nos comunicamos ao atribuir adjetivos aos padrões, tecnologias, comportamentos .
Sobre o pattern em sí, no artigo do Martin Fowler que usei como referência, ele indica quando o pattern deve ser usado, e nesse caso, substituindo o uso de exceptions pois elas mostram apenas o primeiro erro de validação já que travam o fluxo de execução já na primeira falha de validação, os demais erros serão descobertos apenas nas chamadas seguintes. No mesmo artigo, ele mostra um exemplo de código implementando o pattern em uma aplicação Windows Forms com C#, sem fazer uso de exceptions, então na prática, elas nem foram usadas ao aplicar o pattern, da mesma forma como eu fiz.
“The most obvious alternative to using Notification is for the domain to use exception handling to indicate errors. Such an approach has the domain throw an exception if a validation check fails. The problem with this is that it only indicates the first validation error. It’s usually more helpful to show every validation error, particular if validation requires a round trip to a remote domain layer.”
O artigo do Fowler que você usou como referência, é um hands-on de como é possível substituir exceptions por notifications ao fazer validações em um código já existente, um refactoring, e no final do artigo ele entrega um código onde as exceptions foram substituídas por notifications, como eu fiz. Claro, ele como um guru de software e velho de comunidade sabe que deve deixar bem claro que não está abolindo o uso de exceptions e por isso o disclaimer no começo do artigo.
“This article is about replacing exceptions for notification in the context of validating raw input. You may also find this technique useful in other situations where a notification is a better choice than throwing an exception, but I’m focusing on the validation case here, as it is a common one.”
Ao final, ele deixa claro que o objetivo à médio prazo é eliminar completamente o uso de exceptions em que se possa esperar falhas de validações.
“… the medium-term target should be to eliminate the use of exceptions in any circumstance where we might expect validation failures.”
Ainda sobre um dos exemplos que foram usados, em que você citou o System.IO.File.Delete, concordo 100% que ele está correto em lançar exceptions, ele é um método de infraestrutura do framework e como tal não caberia o uso de notification pattern, já que o padrão prega que deve ser usado em validações de domínio, não infraestrutura.
“An object that collects together information about errors and other information in the domain layer and communicates it to the presentation.”
Nesse primeiro ponto, onde você compara teu exemplo, eu senti falta do que seria a implementação de uma operação que necessitasse do Customer válido para prosseguir. Acho que ter uma essa explicação, seria esclarecedora.
Sobre o System.IO.File.Delete, pela perspectiva dele, ele é o domínio. Ele pode ser infraestrutura no seu contexto, mas para ele, então não faz a menor diferença. Essas demandas por assegurar pré-condições e pós-condições não são exclusividade da infraestrutura.
Sobre o exemplo que usei, a entidade Customer não tem métodos para enriquecer ainda mais o exemplo pois não achei necessário já que eu conseguia explicar o conceito do pattern apenas através de validações no construtor, o fluxo não é travado nesse momento, e também não seria nos métodos de alteração de estado caso existissem, pois ao meu ver não é responsabilidade da entidade travar a execução do código, ela deve sim se validar e sinalizar que está inválida, com todas as validações possíveis feitas nesse momento, sem travar a execução. O consumidor, no meu exemplo um command handler, deve verificar se existem erros de validação (integridade da entidade) antes de persistí-la no banco de dados por exemplo, o mesmo comportamento deveria ser feito em alguma abordagem com um Application Service, que na prática orquestram a execução do request e podem bloquear a execução com um simples “return” devolvendo as notificações para o client.
O desenvolvedor deve ser apto a verificar o estado da entidade antes de persistí-la, da mesma forma que deve verificar se um objeto retornado por algum método está nulo antes de fazer alguma operação com ele. Para ambos os casos, testes de unidade pegariam os casos onde o desenvolvedor tivesse esquecido de verificar a integridade dos objetos.
Concordo que a falta de um bloqueio do fluxo de forma nativa no pattern pode causar algum problema em potencial, se não usado corretamente. Ao usar notifications pattern, inevitavelmente será necessário verificar se existem notificações antes de prosseguir com o processamento e assim evitar esses problemas. Disparar uma exception no final para bloquear o fluxo após as validações é algo que eu nunca havia visto ser usado com notification pattern, funciona nesse sentido, mas não é a única forma de se ter esse efeito, além disso, ainda é necessário verificar as notificações.
Eu entendi perfeitamente onde você parou, e citei isso. O ponto-chave é que, como citei em outra resposta de um comentário teu, ter um método qualquer que dependesse do Customer estar válido poderia mudar totalmente a mensagem que seu post entrega.
No exemplo mostrei um cenário hipotético de criação de um Customer com nome e email, não haveriam mais métodos de Customer à serem chamados alí. A criação da instância do Customer se resolveu no construtor e daí surge o estado válido ou inválido dele, que é verificado pela classe que está orquestrando sua execução.
No cenário onde o Customer já esteja criado e uma operação é feita sobre ele, por exemplo um “ChangeEmail” ou qualquer outro, o Customer já válido que foi criado préviamente seria recuperado de alguma fonte de dados, e então a operação seria aplicada sobre ele. Não existe nesse caso uma operação feita sobre um Customer inválido. Ele pode ficar inválido devido à operação, o email pode estar errado, e uma notificação de email inválido seria criada.
Não vejo um cenário onde um Customer fosse criado e no mesmo request seu email alterado. Não estou dizendo que isso é impossível de acontecer, mas de qualquer modo ambos os casos poderiam deixar a entidade em um estado inválido, que deveria ser verificado pela classe que está orquestrando a execução.
Sobre Notification Pattern + Exceptions, eu tenho uma resposta simples que vai de encontro com o que disse.
Não existe validação, produzida por notification pattern que em outra parte código não deva produzir direta ou indiretamente uma exception.
Isso quer dizer que se notification pattern for aplicado em escala, para 100% dos cenários e ainda sim eu tiver para todos estes cenários, suas devidas exceptions prontas para serem lançadas, de fato estas exceptions nunca seriam lançadas de fato, somente em seus testes unitários.
Não importa se estamos falando de domínio rico ou domínio anêmico ou de serviços, se há um pré-requisito não atendido à chamada de um método, esse precisa ser validado e se não atendido, exceptions precisam ser lançadas. Mas se você colocar o Notification Pattern na frente quebrando a validação em notificação + execução do core, você consegue nunca lançar sequer 1 exception, o que não diz que elas serão removidas do código, provavelmente são consolidadas apenas.
O que você está me dizendo é que obrigatóriamente uma validação de domínio que produza uma notificação, deve produzir uma exception.
As validações podem produzir resultados válidos e inválidos, sendo que no caso de um resultado inválido uma notificação deve ser criada informando qual foi a falha. Ambos os cenários são previsíveis e perfeitamente esperados.
A própria definição de Exception diz que elas são excepcionais, para cenários inesperados, o que acontece é que é um recurso simples de usar e funciona para parar o processamento, levando qualquer mensagem para cima na stack.
Como eu disse em outro comentário, usar notification pattern implica que você deverá verificar se existem notificações antes de prosseguir com o processamento, se existir, um return para o client com o resultado das validações já basta e nenhuma exception precisa ser lançada.
No caso o que são consolidadas são as mensagens produzidas pelas validações.
O caso exceptional, é uma condição exigida ter passado para uma etapa em que não deveria ter passado.
Vamos falar sobre um endereço inválido. Um endereço inválido é um problema que poderia ser resolvido com notification pattern durante seu cadastro. Em um cenário de um e-commerce, já durante o processo de dispatch da mercadoria, deveria lançar uma exception.
O fato de você conhecer o problema não faz ele deixar de ser excepcional. Até porque seguindo essa linha de raciocínio não seria possível lançar exception alguma, pois uma vez sendo conhecido, deixaria de ser excepcional.
Não é possível vislumbrar sequer um cenário onde a mesma validação que retornou uma Notification não deva lançar uma exceção EM OUTRO MOMENTO do fluxo.
Se há uma notification, há uma validação, se há uma validação então:
Então ela é uma pré-condição para algo. Se uma pré-condição não é atendida uma exception precisa ser lançada quando não atendida.
Já que não ficou claro, e pelo que vejo não está claro, vou separar um tempo amanhã a escrever sobre isso.
Acho que as coisas estão se misturando aqui.. rs
Vamos lá, uma Exception de um jeito ou de outro é uma forma de notificar que algo deu errado, diante de uma validação para uma pré-condição.
O pattern de notificações é uma forma de levar uma mensagem da camada de domínio para a camada de apresentação, e com isso temos a possibilidade de acumular essas mensagens, sendo que ele normalmente é aplicado para validações de domínio. Não é possível acumular os erros de validação do domínio se eles lançarem exceptions, e é ai que as exceptions são deixadas de serem usadas, apenas nesse cenário. Ao invés de lançar excpetions nas validações de domínio você acumula as mensagens em uma lista de strings por exemplo, e essa lista de strings é retornada para a camada de apresentação.
O fato de lançar uma exceção posterior para uma validação que gerou notificação é simplesmente pelo fato de ter que bloquear o fluxo de execução após as validações não serem satisfeitas, e esse uso de exception também não é necessário se você simplesmente fizer um return.
Eu concluo com isso, que o grande fato de lançar uma exception após as validações gerarem notificações é pura e simplesmente questão de gosto. Você pode lançar a exception para interromper o fluxo, ou usar return.
Se possível, me apresente somente 1 cenário (preferencialmente ligado a um negócio qualquer, hipotético ou real) em que você teria uma notificação em que em outro momento não deveria ter uma exceção.
Olá, eu tenho optado por um misto dos pontos de vistas dos dois. E apresento aqui como tenho adotado para ver as suas considerações. Gosto da forma fluente de escritas das regras como do framework Flunt e também do fato de ter um conjunto de falhas e não só o primeiro erro. Porém também não gosto da idéia de permitir que a entidade esteja em um estado inválido. Assim, faço uso (em projeto de estudo) do Flunt, porém lançando exceções no domínio. Veja como ficou e critica aí…kk https://github.com/maiconcp/viperex/blob/master/Anuncios/Viper.Anuncios.Domain/Entities/Anuncio.cs
Olá Maicon, sua implementação ficou bem interessante, criando um método de extensão ao Flunt para lançar uma DomainException ao final das validações com as mensagens acumuladas, e vai de encontro com o que o Luiz disse, de mudar a exception de lugar, lançando no final. Funciona bem para interromper o fluxo de execução dado o estado inválido das validações.
Ao usar o Flunt, herdando a entidade da clase Notifiable que ele possui, implica que ela poderá ter dois estados (válido e inválido), no exemplo que usei em meu artigo segui essa abordagem, porém usando FluentValidation sem uma classe Notifiable, apenas olhando o resultado das validações para determinar o estado.
Como você mencionou, no seu caso foi uma questão de preferência não optar por ter esse estado na entidade, e por isso o lançamento da exception em momento posterior se faz necessário, nesse caso você não poderia ter uma verificação de estado em uma Application Service por exemplo. No meu exemplo, como posso ter um estado válido ou inválido na entidade, obrigatóriamente tenho que verificar seu estado e então faço um simples “return” para interromper o fluxo de execução na classe que está orquestrando tudo, e ai a exception não foi necessária.
Mas pelo que percebi nessa discussão, e que abordei na segunda versão do meu artigo, que isso é uma questão de preferênca lançar ou não a exception no final, além disso acho que cabe avaliar cada caso e ver se vale a pena lançar a exception. Para a maioria das aplicações o lançamento de exception vai resolver bem o problema.
Mas, a questão que me vem na cabeça sempre que falamos disso, e que observo ao ler sobre boas práticas ao uso de Exceptions, é que conceitualmente elas não deveriam ser usadas para comportamentos previstos ou controle de fluxo. Então fica a questão, devemos ou não usá-las nas validações?… já que elas fazem parte de um comportamento esperado e de certa forma são uma condição para controle de fluxo.
Um aspecto que o Martin Fowler aborda em seu artigo original, é que uma alternativa seria usar eventos para notificar as falhas de validação sem fazer o uso de exceptions, mas não cheguei a ver um exemplo com essa abordagem ainda. Talvez eu tire um tempo para testar isso.
Valeu Obrigado pelo Feedback, optamos por ter a entidade sempre válida, sem utilizar a herança do Notifiable, mas tendo os beneficios das validações concatenadas. A DomainException concatena as mensagens mas também tem uma lista interna das notificações. Na nossa camada de API, temos um ExceptionFilter que retorna um bad request com a lista destas notificações. Logo, a exception é lançada da camada de Domain e atravessa a Application. A questão de ser uma exception genérica pode não agradar a todos, mas queria compartilhar essa abordagem com vocês.
Pois é eu senti falta de um exemplo que demonstrasse a notificação retornando para uma UI ou algo do gênero.
Entendo o propósito de notification e sim, concordo que ele traga para o projeto muitos benefícios, simultaneamente concordo com a abordagem sobre exceptions, só queria ver um exemplo em que simultaneamente víssemos exceptions e notifications quase que lado-lado.
Wellington, acabei só vendo o comentário agora. Perdão.
Não vi a nova versão do post, vou dar uma olhada.
Sobre a questão das exceções, por mais singela que seja a validação, e sejamos práticos, vamos para o exemplo de que um CPF precisa ser válido, por exemplo, em um contexto de e-commerce.
No contexto do cadastro de usuário ou no fluxo em que ele está cadastrando aquilo, podemos tratar com notificações, sem o menor problema.
Mas partindo de um pré-suposto hipotético de que não poderia emitir uma nota fiscal para um CPF inválido, no momento de realizar a tarefa de emitir essa nota, deveria lançar uma exception. O que não impede de haver uma outra notification lá no checkout, antes de fechar a compra ou efetuar o pagamento.
Como disse no texto, não importa onde, o que em um lugar for uma notificação, deverá ser uma exception em outro, e não importa se estamos falando de preenchimento de campos, ou de consistência de CPF’s.
O que pode eventualmente não ter exceções: Validações que não são impeditivas, e isso é comum no ciclo de evolução onde é o cliente quem mantém um cadastro.
Vamos supor que RG ou Identidade não seja um campo obrigatório no passado, mas passou a ser agora. É um caso em que você tem novas notificações, e que só vai poder lançar exceções sobre o tema quando 100% da base estiver preenchida (utópico).
Estou alucinando temas e e exemplos para tentar expressar os cenários.
Imagine o usuário tentando emitir uma nota fiscal em um sistema qualquer:
1 – Este usuário preenche um formulário gigante, com digamos, 50^ campos;
2 – O usuário clica no botão “Emitir NF-e”;
3 – A requisição é enviada ao backend que então, inicia o processo de validação da entidade “NFe”;
4 – No 5º campo é encontrado um problema;
5 – O sistema lança uma exception que devolve o fluxo para o frontend, informando ao usuário que o 5º campo tem um problema;
Porém, existe uma dezena de outros problemas a partir do 20º campo. Mas o usuário ainda não sabe disso, pois o fluxo do backend foi interrompido pela exception no 5º campo da NFe.
Bom, o que quero dizer com esta pequeno exemplo, é que me parece que utilizar um modelo híbrido, talvez fosse a melhor alternativa, ou seja, devolvo notificações para validações simples (Fail Fast Validations) ou de domínio, onde geralmente tempos quase total controle sobre o contexto, e lanço exceções em situações onde o contexto da aplicação fica fora do escopo da minha aplicação, exemplos: Acesso a APIs de terceiros, acesso a banco de dados, acesso ao sistema de arquivos, etc.
Nenhum dos dois modelos invalida o outro. Mas se auto complementam.
O texto trata da demonização das exceptions, é sobre a visão deturpada passada sobre alguns textos, especificamente nos textos dobre Notification Pattern.
Você aponta para cenários onde só alguma validação só é feita com notification. É isso mesmo?
Olá, Luiz Carlos Faria. Também sou contra a demonização das exceptions. As utilizo com muita frequência em meus sistemas. Como disse, apenas em situações onde não tenho como garantir o controle da operação.
E quanto à sua pergunta, sim, existem cenários em que utilizo apenas notificações para interromper o fluxo da operação. Por exemplo, nas validações superficiais, aquelas que não exigem tantos recurso$ de hardware para sua execução.
Geralmente utilizo um modelo semelhante a isso (em um software web):
1 – Frontend -> Validações em frontend, utilizando javascript;
2 – Backend -> Basicamente uma repetição das validações do front end. Aqui utilizo notificações, agrupando todas as falhas de preenchimento (notifications) e devolvo esta lista para o frontend;
3 – Backend -> Validações mais custosas, envolvendo acesso a banco de dados ou quaisquer outros recursos, os quais tenho pouco ou nenhum controle.
Este modelo, tem se mostrado muito viável e econômico, pois o passo 1 evita requisições desnecessárias, enquanto o passo 2 evita o consumo de recursos de hardware do servidor (validações de nível 3).
Por exemplo, imagine que você está construindo uma API REST para emissão da NF-e. Esta API será utilizada para outros sistemas para emissão da nota fiscal eletrônica. O fluxo principal desta API seria, basicamente:
1 – Recebo uma requisição com um JSON contendo os dados da NF-e;
2 – Valido os dados do JSON;
3 – Transformo-o em um XML válido para a NF-e;
4 – Transmito este XML para a SEFAZ de destino (processo assíncrono);
5 – Busco o retorno do processamento na SEFAZ com o protocolo devolvido pela própria SEFAZ no passo 4;
6 – Utilizando, por exemplo, um webhook, notifico o consumidor da minha API sobe o final do processamento da NFe por parte da SEFAZ.
No exemplo a cima, eu utilizaria:
– Notificações para o passo 2;
– Exceptions para os passos 4, 5 e 6.
Existe equívoco na abordagem do autor.
A agregação raiz deve controlar e conhecer a integridade de seu estado (e respectivas agregações). A cada comando a agregação tenta aplicar a mudança de estado e adiciona as notificações necessárias.
É responsabilidade do caso de uso/serviço de domínio verificar o estado da agregação e realizar a interrupção de fluxo, se necessário (necessariamente não é uma excepcionalidade pois os estados são conhecidos: válido ou inválido ).
Realizar a interrupção de fluxo dentro da agregação raiz é uma evasão de responsabilidade do caso de uso/serviço de domínio.
Ao realizar o lançamento de exceções dentro da agregação, não apenas deturpa o padrão de notificações, como traz um uso equivocado de exceções.
Exceção é para momentos onde não há absoluto controle do fluxo. Se os possíveis resultados são conhecidos, qual o sentido de considera isso uma exceção?
Uma exceção deve ser resultado de fluxo/bloco protegido, onde os possíveis resultados não estão sob o controle da aplicação.
Antonio, não faz diferença se é uma agregação ou não. Se você não consegue recuperar o fluxo, com interação do usuário, obtenção de dados por outra via, ou sei lá o que mais. Então é papel, de alguém lançar uma exceção. Não importa quem.
Fowler é preciso em escrever “Replacing Throwing Exceptions with Notification in Validations” onde ele já no título diz, ONDE e QUANDO.
Da mesma forma que diz “I need to stress here, that I’m not advocating getting rid of exceptions throughout your code base. Exceptions are a very useful technique for handling exceptional behavior and getting it away from the main flow of logic. This refactoring is a good one to use only when the outcome signaled by the exception isn’t really exceptional, and thus should be handled through the main logic of the program. The example I’m looking at here, validation, is a common case of that.”.
Portanto, um simples CPF inválido no momento errado, momento esse não recuperável, já seria um bom motivo para uma exception. E se isso é um fato conhecido, é um requisito da operação, sim, deve haver uma exception aqui.
Para toda validação em uma extremidade de um software, há uma exception, ou ao menos deveria existir, sendo lançada sempre que a atividade não for recuperável.
Bom dia, Luiz!
Ultimamente tenho tido uma preocupação muito grande em relação à organização e separação de responsabilidades do código dos meu projetos.
Achei a discussão de vocês dois bem válidas e abre espaço para refletirmos várias formas de implementar e organizar o código.
A dúvida que me trouxe até essa discussão de vocês resumidamente é uma questão de saber:
se é adequado lançar exceções na camada de negócio quando alguma regra não for satisfeita (o saldo para realizar o valor de um saque não é suficiente).
Na postagem de vocês dois percebi que foi dada maior ênfase na validação dos dados de entrada, na meu entendimento, a validação dos dados de entrada é um processo que deve ocorrer no início da camada de negócio ou exatamente antes dela.
Portanto, gostaria de uma sugestão “elegante” de como lidar com as situações que as regras de negócio não são satisfeitas o mais apropriado seria lançar Exceções ou realizar o Notification?
Muito obrigado!
P: “se é adequado lançar exceções na camada de negócio quando alguma regra não for satisfeita”
R: Não só é adequado, como obrigatório, na minha opinião.
Vamos supor que você tenha um uma classe Pessoa e um método Andar. Essa pessoa possui atributos “pernas”, com uma lista que só pode ter 2 itens, perna esquerda e perna direita.
Vamos supor que a perna direita esteja quebrada, ela está presente, mas está com algum status que defina que está quebrada.
Se você chamar o método Andar, em um cenário desses, esse método deveria lançar uma exception dizendo que não pode andar porque a perna está quebrada.
Mas de nada impede, e não há nada de negativo em você ter um método bool PodeAndar(), que retorna um boolean, uma lista de valores, que faça uma validação que se usada corretamente, evitaria que o método Andar fosse chamado quando a checkagem de “PodeAndar” retornar uma informação negativa.
O que quero dizer é que você pode mesclar notification e exceptions. Tudo que de um lado foi uma notificação, de outro deveria ser uma exception. Há cenários em que ela estará, por exemplo, em algo de infraestrutura, há cenários em que ela estará no core.
O que importa é assegurar que se você tem um código, ele valida as condições necessárias e não aceita operações erradas. Perguntas retornam repostas, mas operações executam tarefas.
Uma coisa é verificar se o arquivo existe no disco e tomar uma decisão ou um desvio de fluxo de acordo com a resposta, a outra é mandar deletar ou abrir um arquivo que não existe.
Obrigadão pelo esclarecimento Luiz!
Você conseguiu ser mais claro e objetivo ainda na sua resposta! Valeuzão, mesmo!
Um forte abraço!
Acabei esquecendo de acrescentar na minha pergunta anterior a referência de uma postagem que bagunçou um pouco mais o meu entendimento:
https://reflectoring.io/business-exceptions/
Bom dia, Luiz!
Gostei bastante da discussão levantada por vocês dois, pois vocês apresetaras formas de implementar uma solução, porém quando se trata de
organizar o código e separar responsabildiades de forma adequada, várias decisões que poderiam ser simples ao se fazer “de qualquer forma”
tornam-se bem mais complexas.
Minha dúvida é: qual seria a melhor maneira de lidar com regras de negócio não sendo satisfeitas na camada de negócio do projeto?
Na postagem de vocês dois, percebi que foi dada maior ênfase na validação dos dados de entrada, que no meu entendimento deve ocorrer
no início da camada de negócio ou exatamente antes dela.
Exemplo: qual seria uma sugestão interessante e elegante para uma ação de atualização de um recurso que só pode ser atualizado se um conjunto
de regras fosse satisfeito? O método da camada de negócios, em seu fluxo convencional, deve atualizar o recurso e retorná-lo para o controller.
A dúvida surgiu, pois se alguma regra não for satisfeita e for lançada uma exceção “de negócio” a impressão que tenho é que a exceção estaria sendo
utilizada para desviar o fluxo normal do código.
Além disso, acabei encontrando essa postagem: https://reflectoring.io/business-exceptions/ que sugere que Exceções da camada de negócio
não são adequadas por conta de 5 desvantagens que o autor apresenta.
Porém, nessa postagem não ficou claro para mim uma alternativa para notificar o usuário apropriadamente caso uma regra de negócio não fosse satisfeita.
Enfim, agradeço pela postagem de vocês dois Luiz e Wellington, pois nos permitiram refletir e buscar implementar boas práticas no nosso código!
Muito obrigado!
Cheguei tarde no post, mas não consigo não comentar 🙂
Gostei muito do seu ponto de vista, está muito correto. Só faço um comentário, que acho que ficou implícito: devemos usar exceções, mas não devemos utiliza-las para controlar o fluxo de negócio.
Vejo muita gente criando a BusinessException e utilizando-as no cath para alterar o fluxo.
Bem colocado.
Não é sobre desviar nem controlar fluxo, é sobre interromper e comunicar que outro dev falhou, ou hardware falhou, ou uma condição não suportada foi alcançada.
É sobre interromper e comunicar que foi interrompido de forma EFICIENTE, sem encobrir bugs.
Fala Luiz!
Cheguei bem tarde aqui nessa discussão, mas ainda em tempo de agradecer por ter levantado o tópico. Não vejo muito conteúdo sobre esse assunto e estou buscando formas de modelar melhor a minha forma de codificar e tomar melhores decisões.
Ainda sobre o conceito de exceptions, você acredita que existe algo negativo em disparar as exceções diretamente pela entidade de domínio? Principalmente quando as mesmas são disparadas como efeito de validação dos campos diretamente no construtor.
Eu tinha um conceito em que a validação poderia facilmente ser de responsabilidade da camada de negócio, realizando as validações necessárias antes mesmo de se construir uma entidade e que isso já seria suficiente de certa forma. Porém, agora me deparei com um código lançando exceções de validação diretamente da entidade de domínio e estranhei um pouco essa abordagem. Principalmente pela falta de padrão que foi adotada na montagem das mensagens de erro.
Fiquei com a sensação de que a entidade estava “suja”, mas bem entre aspas mesmo. Lembrando que não sou adepto de entidades anêmicas, mas também não utilizo domínios ricos como algo indispensável.
Valeu Luiz, muito obrigado!!
Depende, tudo aquilo que é um erro sistêmico, ou seja um bug causado por um programador que inadvertidamente configurou um estado errado, ou enviou argumentos errados, deve lançar exception imediatamente.
Não importa se se trata de uma validação ou não.
É indiferente
O que for causado pelo input do usuário, tem de avaliar caso-a-caso.
A discussão sobre validações nos construtores gira em torno de uma discussão mais profunda sobre ter ou não estados inválidos de uma entidade em memória?
Essa é uma discussão que sem confirmação real alguma, minha experiência vendo times decidirem sobre isso, me parece que sim.
Me parece ser convencionado que se pode, sim, ter entidade com estados inválidos, isso porque muitas das vezes só é possível determinar se um estado é válido ou não, se tivemos outros dados para análise, e tirar isso das entidades dá uma natureza anêmica a essas entidades.
É importante ter cuidado porque o que fiz nesse texto não foi uma apologia às exceptions. Tive em vista trazer a tona o quão nocivo e irracional é o discurso de abolição das exceptions. Ele sempre carrega falta de compreensão do que são exceptions e de sua finalidade.
Muito obrigado pela resposta, Luiz! Trouxe uma visão mais ampla sobre o assunto. Abraço