web-dev-qa-db-fra.com

Mappage des exceptions à la réponse d'erreur

Imaginez un programme qui expose un service REST/ service gRPC /quel que soit le service, qui utilise plusieurs bibliothèques tierces. Ces bibliothèques peuvent bien sûr lever des exceptions en cas de problème, par exemple, si l'utilisateur a essayé d'accéder à quelque chose auquel il n'est pas autorisé.

Le problème ici est qu'il n'est pas possible de vérifier au préalable si la demande est correcte ou si l'utilisateur a suffisamment de droits. Nous envoyons donc la demande à la bibliothèque tierce et obtenons une exception.

Est-ce une bonne pratique d'attraper ces exceptions au niveau supérieur et de les mapper à des codes d'état comme celui-ci?

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;

puis renvoyez le code d'état avec un objet d'erreur:

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

Avantages que je vois en utilisant cette approche:

  • Dans le programme, nous n'avons pas à nous soucier si nous exposons un service REST ou un service SOAP ou autre chose. Lancez simplement une exception, ce sera traité correctement plus tard.
  • L'appelant obtient suffisamment d'informations correctes en cas de problème (tant que les exceptions ont des noms et des informations significatifs).
  • La journalisation peut également être centralisée. Toutes les exceptions non gérées seront enregistrées là où nous les convertissons en réponses d'erreur.

Désavantages:

  • Ça fait un peu "hacky".
  • ?

Quelle est la bonne façon de procéder?


Edit: Depuis qu'il a été demandé, voici ce que je fais pour les services gRPC, où le mappage d'erreur est assez similaire:

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);
}
7
CommonGuy

Il n'y a rien de mal à normaliser les exceptions et à les gérer correctement. Votre classe ErrorResponse semble être la bonne approche pour gérer un appel à votre service REST.

S'il y a quelque chose ici qui pourrait être amélioré, c'est une approche de gestion des erreurs statiques. La liste des exceptions/codes d'erreur possibles pourrait facilement s'allonger demain et votre autre va devenir monstrueux à ce rythme.

Pensez à créer une classe ErrorManager contenant une liste d'interfaces ErrorHandler avec deux méthodes: bool CanHandle(Exception) et ErrorResponse Handle(Exception)

Votre ErrorManager lorsqu'il reçoit une exception vérifiera bêtement chaque ErrorHandler enregistré avec un appel à CanHandle à l'exception. Lorsqu'il renvoie true, la vérification s'arrête et vous retournez votre ErrorHandler en appelant la méthode Handle de votre gestionnaire. Vous créez ensuite un ErrorHandler pour chaque ErrorResponse possible (et pas nécessairement pour chaque exception).

Cette approche dynamique vous donne la possibilité d'enregistrer par programme les classes ErrorHandler ou de les charger à partir d'une base de données si vous le souhaitez. Plus important encore, si vous décidez d'enregistrer par programme les classes ErrorHandler maintenant, si vous décidez de changer la façon dont vous chargez ces gestionnaires, vous pouvez le faire avec peu ou pas d'impact sur votre programme tel quel. Essentiellement, vous protégez votre gestion des erreurs dans le futur.

Un mot au sage: ayez un plan pour quand vous ne trouvez aucun gestionnaire pour une exception. Il vaut probablement mieux être un HttpStatusCode.InternalServerError. Bien que vous puissiez créer un gestionnaire qui "gère toujours" les exceptions, je le déconseille, car techniquement, nous parlons de gérer conn les exceptions possibles et toute exception qui n'est pas attendue est une exception inconnue et devrait très certainement être signalée en rouge et non pas simplement traitée comme toute autre erreur.

6
Neil

J'aime votre approche, sauf que je traduirais également ce qui va au client dans le corps de la réponse d'erreur. Comme vous dites que vous n'avez aucun contrôle sur les bibliothèques tierces, vous ne savez pas non plus si elles vont divulguer des informations sensibles au client. Mieux vaut prévenir que guérir: enregistrez l'exception dans vos journaux internes et propagez uniquement le minimum d'informations dont le client a besoin pour connaître l'erreur.

1
user285148