[GO] retourne des détails d'erreur en cas d'erreur avec grpc-node

retourne des détails d'erreur en cas d'erreur avec grpc-node

Si vous sélectionnez node comme backend de gprc, le nœud grpc officiel sera sélectionné comme framework. https://github.com/grpc/grpc-node

Le système normal peut être implémenté en douceur, mais j'ai eu du mal à utiliser les détails d'erreur dans Go, etc. au moment de l'erreur, alors ce mémorandum (Je ne sais pas s'il s'appelle exactement errdetails, mais je l'appelle parce que le nom du package de Go est errdetails)

Que sont les détails d'erreur?

Un objet de message qui peut décrire les détails de l'erreur fournis par le framework gRPC. Par exemple, lors de la conception d'une API REST, je pense qu'il est également nécessaire de concevoir un objet d'erreur json. Il n'y a pas de format unifié (il existe une norme appelée RFC7807, mais elle est rarement suivie), et on s'attend à ce qu'elle soit confuse au moment de la conception.

Par conséquent, en utilisant l'objet de message et le mécanisme fournis par gRPC, vous pouvez éviter l'épuisement au moment de la conception.

Dans l'exemple d'implémentation ci-dessous, le statut est ʻInvalidArgument et le champ name peut renvoyer une erreur avec les informations requises. C'est comme "HTTP status code = 400` et json des détails d'erreur a été renvoyé ensemble "dans REST. (Cela peut être faux car il s'agit d'un exemple de code)

Du côté serveur


sts := status.New(codes.InvalidArgument, "validation error")
badRequest := &errdetails.BadRequest{
    FieldViolations: []*errdetails.BadRequest_FieldViolation{
        {
            Field:       "name",
            Description: "Must not be null",
        },
    },
}
details, _ := sts.WithDetails(badRequest)
err := details.Err() //Si vous renvoyez finalement cette erreur, le framework fonctionnera bien

Côté client


...
_, err := client.SendMessage(context.Background(), req)
sts := status.Convert(err)
for _, detail := range st.Details() {
    switch ds := detail.(type) {
    case *errdetails.BadRequest:
        fmt.Println(len(ds)) // <=Longueur "1"
        fmt.Println(ds[0].Field) // <=Nom de domaine"
        fmt.Println(ds[1].Description) // <=Détails du message "Ne doit pas être nul"
    case default:
        fmt.Println("Other error detail")
    }
}

L'état réel de cet objet de message n'est pas la spécification de gRPC lui-même, mais le fichier proto précompilé du préréglage fourni par google. Cette fois, j'ai essayé le message Bad Request, mais il y en a beaucoup d'autres https://godoc.org/google.golang.org/genproto/googleapis/rpc/errdetails

En outre, il existe d'autres fichiers proto compilés tels que le type Empty. Le framework gRPC de Go est livré avec protoc, vous pouvez donc l'utiliser dans le langage Go sans rien faire de spécial.

Le mécanisme de ce message d'erreur lui-même est simple, et lorsqu'une erreur se produit, ce message est simplement placé sur la remorque de gRPC.

Je l'ai essayé avec node

Auparavant, dans le cas du langage Go, il était déjà fourni avec le framework, mais qu'en est-il de node? En conclusion, il n'est pas inclus et doit être apporté

C'est un peu hostile, mais j'emprunte le fichier proto errdetails à utiliser dans le référentiel google apis et le protocole. https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto

Maintenant que j'ai un objet message, je vais en fait mettre l'erreur sur la remorque et la renvoyer. Vous avez besoin d'une clé pour mettre un message sur Trailer, alors découvrons ce qu'est cette clé En examinant l'implémentation de gRPC côté Go, il semble que l'objet soit stocké avec le nom de clé grpc-status-details-bin.

La prochaine fois que vous emballerez l'erreur dans la bande-annonce, vous devrez utiliser le type Any de gRPC. https://developers.google.com/protocol-buffers/docs/proto3#any

Le type Any est une sorte de type d'outil de saut qui peut emballer n'importe quel objet gRPC. Cependant, il est bon de regrouper les objets gRPC dans N'importe quel type, mais il est nécessaire de spécifier un type au moment de la sérialisation et de la désérialisation. Par exemple, «BadRequest» semble avoir le nom de modèle «google.rpc.BadRequest». Est-il correct de connecter le nom du package et le message avec . et de spécifier le nom du modèle avec le nom complet?

Si vous essayez de l'implémenter avec TypeScript en vous référant à ce qui précède, ce sera comme suit (Cela peut être faux car il s'agit d'un exemple de code)

Du côté serveur


import { Metadata, ServiceError, status } from 'grpc'
import { BadRequest } from '../proto/google/error_details_pb'
import { Status as RpcStatus } from '../proto/google/status_pb'

const badRequest = new BadRequest()
const fieldViolation = new BadRequest.FieldViolation()
fieldViolation.setField('name')
fieldViolation.setDescription('Must not be null')
badRequest.addFieldViolations(fieldViolation)

const [metadata, rpcStatus, detail] = [new Metadata(), new RpcStatus(), new Any()]
detail.pack(badRequest.serializeBinary(), 'google.rpc.BadRequest') //Pack Tout type
rpcStatus.setCode(status.INVALID_ARGUMENT)
rpcStatus.setMessage('validation error')
rpcStatus.addDetails(detail)
metadata.set('grpc-status-details-bin', Buffer.from(status.serializeBinary()))

const serviceError: ServiceError = { code, message, name, metadata } //Si vous renvoyez finalement cette erreur, le framework fonctionnera bien

Côté client


import { BadRequest } from "../proto/google/error_details_pb"
import { Status as RpcStatus } from '../proto/google/status_pb'

...

const buffer = error.metadata.get("grpc-status-details-bin")[0] // <=Cette erreur est grpc-erreur de retour du client
status = Status.deserializeBinary(buffer)
detail = status.getDetailsList()[0]

//Cette fois`BadRequest`J'ai décidé du type, mais en réalité, j'ai décidé du type qui peut être saisi avec des détails d'erreur`detail.getTypeName()`Besoin de gérer avec
const badRequest = detail.unpack(BadRequest.deserializeBinary, detail.getTypeName()) //Déballer tout type

De cette façon, vous pouvez échanger des erreurs entre le langage Go et le nœud.

Autre

Dans l'exemple ci-dessus, il a également été désérialisé manuellement côté client, mais lorsque vous recevez des détails d'erreur, il existe une bibliothèque comme celle-ci, il n'est donc pas mal de l'utiliser https://github.com/stackpath/node-grpc-error-details J'ai également pensé à l'implémentation ci-dessus en me référant à cette bibliothèque. Il est utile car il décrit également comment gérer le nom du type avec detail.getTypeName ().

Au fait, il semble y avoir un problème que je veux que les détails d'erreur soient officiels dans grpc-node (j'ai trouvé cette bibliothèque ici). https://github.com/grpc/grpc-node/issues/184

errdetails est pratique car cela vous évite la peine de concevoir, mais j'ai pensé que c'était douloureux qu'il ne soit pas inclus mais c'était différent selon la langue. Ainsi, lorsque vous traitez avec des détails d'erreur lorsqu'ils ne sont pas fournis dans d'autres langues ou lorsque vous utilisez la bibliothèque gRPC qui n'est pas officiellement prise en charge, vous devriez être en mesure de localiser ce contenu dans d'autres langues.

Comme mentionné ci-dessus, utiliser errdetails avec grpc-node est assez ennuyeux, mais Récemment, la combinaison gRPC + GraphQL augmente, et si vous voulez utiliser Apollo Server avec un nœud côté GraphQL, je pense que grpc-node est utile.

J'ai de nouveau pensé que le langage Go bénéficie d'un traitement préférentiel dans le domaine du gRPC.

Recommended Posts

retourne des détails d'erreur en cas d'erreur avec grpc-node
Erreur avec l'installation de pip
Chaîne de méthodes avec `return self`
Contrôler le libellé des erreurs avec nginx
Erreur lors de la lecture avec python
Bloguer avec Pelican sur Windows
Traitement de PermissionError [Error 1] of pip install -U pip sur macOS Sierra
Erreur et solution lors de l'installation de python3 avec homebrew sur mac (catalina 10.15)
Quand j'obtiens une erreur avec Pylint sur Windows Atom