Mapeando exceções para resposta de erro

5

Imagine um programa que expõe um serviço REST / serviço gRPC / qualquer serviço, que use várias bibliotecas de terceiros. Essas bibliotecas podem, é claro, lançar exceções se algo der errado, por exemplo, se o usuário tentar acessar algo que não é permitido.

O problema aqui é que não é possível verificar de antemão se a solicitação está correta ou se o usuário tem direitos suficientes. Então, enviamos a solicitação para a biblioteca de terceiros e recebemos uma exceção.

É uma boa prática pegar essas exceções no nível superior e mapeá-las para códigos de status como esse?

var statusCode = HttpStatusCode.InternalServerError;
if (ex is ArgumentException || ex is ResourceDoesntExistException)
    statusCode = HttpStatusCode.BadRequest;
else if (ex is UnauthorizedAccessException)
    statusCode = HttpStatusCode.Forbidden;
else if (ex is CustomerNotFoundException)
    statusCode = HttpStatusCode.NotFound;

e, em seguida, retornar o código de status com um objeto de erro:

return new ErrorResponse
{
    Error = new ErrorDescription
    {
        Message = ex.Message,
        Type = ex.GetType().Name
    }
};

Vantagens que vejo ao usar essa abordagem:

  • No programa, não precisamos nos preocupar se expormos um serviço REST ou um serviço SOAP ou o que for. Basta lançar uma exceção, que será tratada corretamente mais tarde.
  • O chamador recebe informações suficientes e corretas se algo der errado (contanto que as exceções tenham nomes e informações significativos).
  • O registro também pode ser centralizado. Todas as exceções não tratadas serão registradas exatamente onde as convertemos em respostas de erro.

Desvantagens:

  • Parece um pouco "hacky".
  • ?

Qual é a maneira correta de fazer isso?

Editar: Desde que foi solicitado, aqui está o que eu faço para os serviços do gRPC, onde o mapeamento de erros é bastante semelhante:

private RpcException GenerateRpcException(Exception ex)
{
    var statusCode = StatusCode.Unknown;

    if (ex is ArgumentException || ex is ResourceDoesntExistException)
        statusCode = StatusCode.InvalidArgument;
    else if (ex is UnauthorizedAccessException)
        statusCode = StatusCode.PermissionDenied;
    else if (ex is CustomerNotFoundException)
        statusCode = StatusCode.NotFound;

    var status = new Status(statusCode, ex.Message);
    var exceptionName = ex.GetType().Name;

    var rpcMetadata = new Metadata
    {
        { "exception_name", exceptionName }
    };
    return new RpcException(status, rpcMetadata);
}
    
por CommonGuy 07.02.2018 / 09:45
fonte

2 respostas

4

Não há nada de errado em normalizar exceções e manipulá-las corretamente. Sua classe ErrorResponse parece ser a abordagem correta para manipular uma chamada para seu serviço REST.

Se houver algo aqui que possa ser melhorado, é uma abordagem de tratamento de erros estáticos. A lista de possíveis exceções / códigos de erro pode facilmente crescer amanhã e o seu if else vai crescer para ser monstruoso nesse ritmo.

Considere criar uma classe ErrorManager que contenha uma lista de ErrorHandler interfaces com dois métodos: bool CanHandle(Exception) e ErrorResponse Handle(Exception)

Seu ErrorManager , quando receber uma exceção, verificará estupidamente cada ErrorHandler registrado com uma chamada para CanHandle , exceto a exceção. Quando ela retornar true, a verificação será interrompida e você retornará seu ErrorHandler de chamar o método Handle do seu manipulador. Em seguida, você cria um ErrorHandler para cada possível ErrorResponse (e não necessariamente para cada exceção).

Esta abordagem dinâmica oferece a flexibilidade para registrar programaticamente as classes ErrorHandler ou carregá-las a partir de um banco de dados, se assim desejar. Mais importante do que isso, se você decidir registrar programaticamente as classes ErrorHandler agora, caso decida alterar o modo de carregamento desses manipuladores, poderá fazê-lo com pouco ou nenhum impacto em seu programa como está. Em essência, você está protegendo o seu erro no futuro.

Uma palavra para o sábio: tenha um plano para quando você não encontrar nenhum manipulador para uma exceção. Provavelmente, é melhor deixar isso para ser um HttpStatusCode.InternalServerError . Embora você possa criar um manipulador que "sempre lida" com exceções, eu aconselharia contra ele, já que tecnicamente estamos falando sobre o gerenciamento de exceções possíveis conhecidas e qualquer exceção que não é esperada é uma exceção desconhecida e certamente deve ser sinalizado de vermelho e não meramente tratado como qualquer outro erro.

    
por 07.02.2018 / 11:45
fonte
1

Eu gosto da sua abordagem, exceto que eu também traduzia o que vai para o cliente no corpo da resposta de erro. Como você diz que não tem controle sobre as bibliotecas de terceiros, também não sabe se elas vão vazar informações confidenciais para o cliente. É melhor prevenir do que remediar: registre a exceção em seus logs internos e propague apenas as informações mínimas que o cliente precisa saber sobre o erro.

    
por 07.02.2018 / 23:05
fonte