Sugestoes para tratamento de exceções

Olá pessoal,

Estou desenvolvendo uma aplicação web com JSF 2, Spring e JPA 2 rodando em um Tomcat. Andei dando uma lida sobre tratamento de exceções até que vi o Fault Barrier Pattern. Gostaria da opinião de vocês sobre o modo de tratar as exceções:

A aplicação está divida em 3 camadas: Controllers > Services > DAOs

A idéia é a seguinte: todos os componentes na camada de Serviço e Persistência podem lançar exceções, mas ela seriam apenas capturadas no Controller (para casos em que não há como se recuperar do erro ou nada precise ser realizado). Assim, o Controller apenas loga (se necessário) e exibe uma mensagem de erro ao usuário do front-end.

Exceptions que necessitariam de um código específico (IOExceptions, na qual precisamos fechar ou dar flush em algum buffer ou re-tentar executar uma query durante uma DataAccessException ou NoResultFoundException) seriam tratadas na própria camada, e depois “encapadas” em uma RuntimeException a ser capturada apenas no Controller com a mensagem ao usuário, ou apenas seriam tratadas e o fluxo do programa continuaria normalmente.

Para não repetir uma porrada de try/catchs nos métodos do Controller, estava pensando em utilizar o Strategy Pattern para o tratamento de exceções. Algo mais ou menos assim:

public interface ExceptionHandler<T extends Exception> {

	void handle(T e);
}

E no controller eu teria uma fábrica para recuperar a implementação correta de ExceptionHandler, e invocaria apenas handle(). Assim eu conseguiria encapsular os diversos tipos de tratamento de exceção diferentes sem colocar n catchs.

Parece uma má idéia?
Combinar os dois padrões seria algo desnecessário?
Algum de vocês já aplicou algum padrão melhor para exceções? Poderia me mostrar ou sugerir melhorias?

Abraços a todos :slight_smile:

Eu faria esse tratamento no front-controller assim você controlaria e um só lugar seu tratamento e neste caso ele conseguiria encaminhar o usuário para um pagina de erro especifica, se você quisesse.
Caso você não tenha explicito na sua aplicação com cara que faz o papel de front-controller você pode colocar um interceptor que receberia todas as requisições caso houvesse uma
exception você poderia capturar no seu interceptor e fazer o tratamento adequado.

Com isso ficaria invisivel o tratamento no controller :slight_smile:

No caso do serviço e dao eu subo e faço o tratamento em um aspecto usando um advice After Throwing, assim ficaria limpa a camada de serviço e DAO.

A modo que você pensou não esta errado, porem quanto mais invisivel melhor :slight_smile:

abraço!

[quote=Leonardo Gaona]Olá pessoal,

Estou desenvolvendo uma aplicação web com JSF 2, Spring e JPA 2 rodando em um Tomcat. Andei dando uma lida sobre tratamento de exceções até que vi o Fault Barrier Pattern. Gostaria da opinião de vocês sobre o modo de tratar as exceções:

A aplicação está divida em 3 camadas: Controllers > Services > DAOs

A idéia é a seguinte: todos os componentes na camada de Serviço e Persistência podem lançar exceções, mas ela seriam apenas capturadas no Controller (para casos em que não há como se recuperar do erro ou nada precise ser realizado). Assim, o Controller apenas loga (se necessário) e exibe uma mensagem de erro ao usuário do front-end.

Exceptions que necessitariam de um código específico (IOExceptions, na qual precisamos fechar ou dar flush em algum buffer ou re-tentar executar uma query durante uma DataAccessException ou NoResultFoundException) seriam tratadas na própria camada, e depois “encapadas” em uma RuntimeException a ser capturada apenas no Controller com a mensagem ao usuário, ou apenas seriam tratadas e o fluxo do programa continuaria normalmente.

Para não repetir uma porrada de try/catchs nos métodos do Controller, estava pensando em utilizar o Strategy Pattern para o tratamento de exceções. Algo mais ou menos assim:

public interface ExceptionHandler<T extends Exception> {

	void handle(T e);
}

E no controller eu teria uma fábrica para recuperar a implementação correta de ExceptionHandler, e invocaria apenas handle(). Assim eu conseguiria encapsular os diversos tipos de tratamento de exceção diferentes sem colocar n catchs.

Parece uma má idéia?
Combinar os dois padrões seria algo desnecessário?
Algum de vocês já aplicou algum padrão melhor para exceções? Poderia me mostrar ou sugerir melhorias?

[/quote]

O ExceptionHandler pode tratar todas as exceções. Aquilo que vc chama de factory é na realidade um locator. Pode usar isso, mas é muito mais complexo do que necessário (mas é possivel)

Dê uma olhada em
http://www.javabuilding.com/academy/java-language/excecoes-conceitos.html
http://www.javabuilding.com/academy/java-language/excecoes-boas-praticas.html

Você deve criar uma categoria de exceções para cada camada do seu sistema. As classes publicas da camada, devem controlar as exceções dando sempre try-catch e chamando o exceptionhandler (que pode ser apenas um método dentro da classe ) Se o método pode tratar a exceção, simplesmente o faz e não chamada o exception handler.

Um melhor codigo para o excption handler é assim


 protected LayerSpecificException handle(Exception e);

Onde LayerSpecificException é classe de Exceção que é mãe de todas as classes de exceção daquela camada. Por exemplo, na camada de serviço teriamos um ServiceException, no acesso dados um DataAccessException, etc…

Usa assim


public void algumMetodo(String parametros){

    try { 

           // faz algo 
   } catch (ExceptionQueEuSeiResolver e) {

         // faço tratamento e não relanço exceção nenhuma
 
   } catch (Exception e){
           // todas as outras
          return handleException(e);
   }

}

O exception handler funciona assim :

  • se a exceção já é uma exceção da camada corrent , simplesmente devolve essa e pronto
  • se é possivel converter a exceção recebida numa exceção da hierarquia das exceções da camada, faça isso e retorne.
  • se não é possivel analizar a exceção, encapsule ela em uma instancia de LayerSpecificException e retorne.

por exemplo


protected ServiceException handle(Exception e){

        if (e instanceof ServiceException ){
                  return (ServiceException ) e;
        }

        if (e instanceof XPTOException ){
             // por regra de negocio eu sei que XPTOEception significa que não ha comunicaçao com um serviço XPTO que estou usando, então eu 
// transformo isso para 

            return new ExternalServiceComunicationImpossible(e);  // repare que mantenho referencia à exceção original. a causa é encapsulada para apecer no stacktrace depois
       }

     // o resto dou um tratamento apenas de encapsular
       return new ServiceException(e);
}

O ExceptionHandler também é um padrão. Vc pode usar o Strategy para implementá-lo, mas normalmente não faz muito sentido ( O que faria sentido seria um Chain of Responsability) e não é muito prático.
Um simples método pode resolver.

E como vc bem disse apenas o ExceptionHandler da camada mais exterior ao sistema é que loga o exception. Esta regra pode ser quebrada se ha comunicação entre nodos. E só na camada de UI que a exceção vira uma mensagem para o usuário. Por causa disso é normal que as exceçtions sejam mais completas com informações não apenas do que de erro mas parâmetros também. I18n pode ser necessário também.

Opa, idéias começam a aparecer hehe

Sergio, deixa ver se entendi.

No caso cada camada de componentes possui uma RuntimeException própria, na qual todo tipo de exceção não tratável é encapsulada dentro dela?

Exemplo: Se meu DAO retornar um ConstraintViolationException durante um update (não é tratável, os dados recebidos são inválidos), eu encapsulo essa ConstraintViolationException e a lanço assim:


 Usuario usuario = null;

 try {
    usuario = entityManager.find(Usuario.class, id);
 } catch (NoResultException e) {
    //sem tratamento necessário, não lança outra exception
 } catch (ConstraintViolationException e) {
   //não recuperável
    throw new DAOException(e);
 } catch (Exception e) {
   //não recuperável
    throw new DAOException(e);
 }
 
 return usuario;

E ai no Controller do JSF eu apenas exibo a mensagem pro usuário e logo o erro se necessário certo? Poderia até fazer um único tipo de Exception, que receberia um enum com a causa do erro/mensagem a ser exibida na UI e e a exception de causa para ser logada.

Obrigado pelas contribuições. Vamos aquecer isso aqui com mais idéias haha, tratamento de exceptions é um assunto que dá debate.

[quote=Leonardo Gaona]Opa, idéias começam a aparecer hehe

Sergio, deixa ver se entendi.

No caso cada camada de componentes possui uma RuntimeException própria, na qual todo tipo de exceção não tratável é encapsulada dentro dela?

Exemplo: Se meu DAO retornar um ConstraintViolationException durante um update (não é tratável, os dados recebidos são inválidos), eu encapsulo essa ConstraintViolationException e a lanço assim:


 Usuario usuario = null;

 try {
    usuario = entityManager.find(Usuario.class, id);
 } catch (NoResultException e) {
    //sem tratamento necessário, não lança outra exception
 } catch (ConstraintViolationException e) {
   //não recuperável
    throw new DAOException(e);
 } catch (Exception e) {
   //não recuperável
    throw new DAOException(e);
 }
 
 return usuario;

E ai no Controller do JSF eu apenas exibo a mensagem pro usuário e logo o erro se necessário certo? Poderia até fazer um único tipo de Exception, que receberia um enum com a causa do erro/mensagem a ser exibida na UI e e a exception de causa para ser logada.

Obrigado pelas contribuições. Vamos aquecer isso aqui com mais idéias haha, tratamento de exceptions é um assunto que dá debate.[/quote]

Debate, sim, mas as regras são bem definidas.
No seu caso um melhor codigo seria assim :

 try {
        return entityManager.find(Usuario.class, id);
} catch (NoResultException e) {
     // o tratamento é retornal null
          return null;
 } catch (Exception e) {
    throw new DAOException(e);
 }

Encapsular o ConstraintViolationException no DAOException não é diferente desde novo codigo. Só seria util se vc estivesse retornando uma exceção própria filha de DAOException algo como DAOConstraintViolationException extends DAOException.
Isso seria importante para, depois , vc saber dar uma mensagem mais amigável. Em vez de “deu erro , eu não sei qual foi” vc pode mostrar “deu um problema de violação de constrangimento”.

O problema sãos os termos : tratar uma exceção significa resolver o problema, o resto é lidar (handle) com a exceção. Exception handling é Lidar com Exceções. Isso singifica saber capturá-las, trasnformá-las, encaspula-las, etc… se vc sabe resolver o problema como no caso do NoResultException, então não é mais problema :slight_smile: . A exceção morre porque ha outra forma de resolver. ( o exemplo classico é escrever num arquivo que não existe. simples, cria-se o arquivo e tenta de novo)

Faltou referir o assunto do enum. Não é uma boa ideia. Vc vai substituir uma hierarquia de exceptions por uma de enums. Não é muito legal.
Para exibir a mensagem simplesmente utilize uma exceção para cada problema que poder existir. Dentro da exceção crie uma string com um key de i18n. depois na ui é só traduzir essa key algo como


public interface InternacionalizedException {
       
 public String getMessageKey();

 public Objec[] getMessageParams();

}


public class DAOConstraintViolationException extends RuntimeException implements InternacionalizedException  {


 public DAOConstraintViolationException () {

}

  public String getMessageKey() {
       return "exception.data.access.constrain.violation"; 
  } 

 public Objec[] getMessageParams();
   return new Object[0];
}

Para outras exceções como coisas que não estão entre intervales ou coisas que são maiores ou menores que um certo valor, é util ter o MessageParams.
Na ui vc usa as API comuns de i18n para procurar a key num arquivo e usar o MessageFormat para inserir os parametros ( do tipo “Valor não está entre {0] e {1}” ).

No caso desta violação de constrangimento vc poderia tentar analizar a exceção para descobrir o que aconteceu. Normalmente no SQLEXception o codigo indica o que realmente está acontecendo.
Por exemplo, se fosse uma chave primária duplicada , vc poderia criar uma exceção assim

public class DAOPrimaryKeyViolationExceptionextends RuntimeException implements InternacionalizedException  {


 public DAOPrimaryKeyViolationException() {

}

  public String getMessageKey() {
       return "exception.data.access.constrain.violation.primarykey"; 
  } 

 public Objec[] getMessageParams();
   return new Object[0];
}

Se conseguir saber em qual tabela é ainda melhor.

public class DAOPrimaryKeyViolationExceptionextends RuntimeException implements InternacionalizedException  {

  private String tabela; 
 public DAOPrimaryKeyViolationException(String tabela) {
    this.tabela = tabela;
}

  public String getMessageKey() {
       return "exception.data.access.constrain.violation.primarykey"; 
  } 

 public Objec[] getMessageParams();
   return new Object[]{tabela};
}

e depois no arquivo de i18n

exception.data.access.constrain.violation.primarykey=“Aconteceu uma violação de chave primária na tabela {0}.”

Obrigado pelas dicas, vou começar a trabalhar no meu modelo :wink:

Estou trabalhando num projeto com JBoss Seam 3 e ele tem um recurso legal para tratamento de exceção. Minha classe para tratar as exceções fica bem simples…segue um esboço:

@HandlesExceptions
public class GenericExceptionHandler {

	void handleExceptions(@Handles CaughtException<Exception> evt, FacesContext facesContext){
		//tratamento específico para exceções genéricas
	}

	void handleViewExpiredExceptions(@Handles CaughtException<ViewExpiredException> evt){
		//tratamento específico para ViewExpiredException
	}
	
	void handleBeanViolationExceptions(@Handles CaughtException<ConstraintViolationException> evt,FacesContext facesContext){
		//tratamento específico para ConstraintViolationException
	}

        //outros tratadores
}

Exceções que envolvem o uso de recursos (arquivos, conexões) eu capturo localmente, fecho os recursos, encapsulo a exceção e relanço-a ficando a cargo do meu tratador de exceção manipulá-la.