Background Image
TECHNOLOGIE

Le contexte du Go

Paul Ferguson - Headshot
Paul Ferguson
Consultant principal

April 11, 2023 | 5 Lecture minute

Je suis un développeur Java de longue date qui a eu l'occasion d'apprendre Go lors du développement d'une application pour mon client. Il y a de nombreuses similitudes qui ont rendu l'expérience assez facile, mais il y a eu quelques problèmes que j'ai découverts en cours de route. Celui dont je veux parler maintenant est le contexte Go.

Dans une application Java, lorsqu'une requête arrive, elle est entièrement traitée et une réponse est renvoyée.

Dans une application Go, il existe un contexte qui rend les choses plus intéressantes. Un contexte transporte les délais, les signaux d'annulation et d'autres valeurs liées aux requêtes à travers les limites de l'API et entre les processus. Lors de l'utilisation d'une requête, il peut être utilisé pour annuler ou temporiser la requête. Une annulation peut se produire pour de nombreuses raisons différentes. L'utilisateur peut fermer son navigateur ou cliquer sur "Annuler" pendant le traitement de la demande. Il peut y avoir une mauvaise connexion internet ou un autre problème de réseau. Un client peut décider que la requête prend trop de temps et la fermer. Quelle que soit la raison, le contexte signalera qu'il est terminé. Cela signifie que le travail qui n'a pas encore été effectué peut être ignoré.

Certains paquets pour les bases de données, les clients http et d'autres nécessiteront qu'un contexte leur soit transmis. Les autres auront probablement un moyen de le définir. Au départ, je n'ai pas compris pourquoi. Il y avait déjà un contexte défini dans la requête, je l'ai donc simplement transmis. Lorsqu'un contexte était annulé, nous avons commencé à voir des erreurs de contexte annulé. C'est parce que ces paquets agissent sur ce signal de contexte terminé en arrêtant tous leurs traitements et en renvoyant une erreur.

ctx := request.Context()

// recherche d'une personne dans la base de données person, err := databaseCall(ctx)

...

// databaseCall Recherche une personne dans la base de données. 

funcdatabaseCall(ctxcontext.Context) (Person, error) {

logInfo(ctx, "Making the database call")

var person Personne

   // pause pour permettre au contexte d'être annulé 

   err := pause(ctx)

if err != nil {

return person, err

}

   // le paquetage de base de données populaire pgxpostgres exige qu'un contexte soit défini dans la plupart des opérations

   connection, err := pgx.Connect(ctx,

"postgres://postgres:postgres@localhost:5432/postgres")

if err != nil {

      // en plus des erreurs habituelles, si le paquet pgx remarque que le contexte est terminé, il renverra une erreur 

      return person, err

}

defer connection.Close(ctx)

   // demande une personne à la base de données et remplit les valeurs de sa structure 

  err = connection.QueryRow(ctx, "select name from

people").Scan(&person.Name)

return person, err

}

Pour voir le code complet, consultez mon application d'exemple sur GitHub à l'adresse suivante https://github.com/paul-ferguson/the-go-context

Remarquez que cette application vérifie si l'appel à la base de données renvoie une erreur. S'il y a une erreur, nous vérifions si le contexte est terminé. Si c'est le cas, l'application ne fait aucun traitement supplémentaire et retourne simplement. Elle n'a même pas besoin de renvoyer un code d'erreur http ou une réponse JSON.

Si vous voulez qu'une application Go fonctionne comme une application Java, vous devez utiliser le contexte approprié. Disons que nous avons un point de terminaison REST qui traite un post-call pour sauvegarder des données. Nous voulons que ces données soient entièrement traitées et transférées dans la base de données, même si le contexte de la demande est annulé. Dans ce cas, nous pouvons utiliser context.Background() pour créer un nouveau contexte qui ne pourra jamais être annulé. J'en ai montré un exemple ci-dessous. J'ai également inclus dans le code commenté la méthode context.WithCancel(), context.WithDeadline() ou context.WithTimeout(). Elles définissent respectivement une méthode d'annulation et une date limite ou un délai d'attente/durée qui déclenchera le signal context done. A context.Background() ne devrait être utilisé que dans certains scénarios : fonctions principales, initialisation et tests, et en tant que contexte de premier niveau pour les demandes entrantes. Notre cas d'utilisation est ce dernier. Dans la plupart des cas, il est préférable d'utiliser l'une des autres options.

ctx:= context.Background()

/* c'est ce qu'il faut faire : Essayez plutôt ces autres options :

// ceci renvoie un contexte et une fonction que nous avons appelée cancel

ctx, cancel := context.WithCancel(request.Context())

// l'appel à cancel déclenchera le signal done

cancel()

// ceci déclenchera un timeout après 2 secondes

ctx, cancel := context.WithTimeout(request.Context(), time.Second * 2)

// d'après la documentation de Go : Même si ctx sera expiré, c'est une bonne pratique d'appeler sa fonction d'annulation dans tous les cas. Sinon, le contexte et son parent peuvent rester en vie plus longtemps que nécessaire. defer cancel() // ceci déclenchera un timeout après un délai de 2 secondes dans le futur ctx, cancel := context.WithDeadline(request.Context(), time.Now().Add(time.Second * 2)) defer cancel()

*/

...

// restCall Recherche une personne en effectuant un appel de repos. 

funcrestCall(ctxcontext.Context) (Personne, erreur) {

logInfo(ctx, "Making the rest call")

var personne Personne // créer la requête get vers le point de terminaison côté serveur 

   request, err := http.NewRequestWithContext(ctx, "GET",

"http://localhost:8080/server-side-get", nil)

   /* 

essayez ceci : Si nous ne transmettons pas le contexte, la requête ne sera pas annulée lorsqu'un signal "done" se produira. La requête 

demande sera entièrement traitée, ce qui gaspillera des ressources. 

request, err := http.NewRequest("GET", "http://localhost:8080/server-side-get", nil) 

*/ 

// transmet l'identifiant de la requête dans l'en-tête, ce qui nous permet de tracer cette requête 

   request.Header.Add(requestIDHeaderKey, ctx.Value(requestIDContextKey).(string))

   // effectuer la requête 

   response, err := http.DefaultClient.Do(request)

if err != nil {

      //todo 

      return person, err

}

// lire le corps complet de la réponse 

   body, err := io.ReadAll(response.Body)

if err != nil {

return person, err

}

// fermer le corps de la réponse 

   err = response.Body.Close()

if err != nil {

return person, err

}

   // décomposer le contenu du corps de la réponse en une structure de personne 

   err = json.Unmarshal(body, &person) return person, err

}

Pour voir le code complet, consultez mon exemple d'application sur GitHub à l'adresse https://github.com/paul-ferguson/the-go-context

Enfin, pour être complet, je devrais montrer comment le contexte peut être utilisé pour passer des valeurs à l'échelle de la requête à travers les frontières de l'API et entre les processus.J'ai déjà utilisé cela pour enregistrer des valeurs communes, comme l'identifiant d'une requête.Voici un exemple.

const requestIDHeaderKey= "request-id"

const requestIDContextKey= contextKey(requestIDHeaderKey)

...

// définir l'identifiant de la demande comme valeur dans le contexte 

requestId:= request.Header.Get("request-id")

if requestId== "" {

   // aucun identifiant de demande n'a été défini, il faut donc en créer un unique 

   requestId= uuid.New().String()

}

ctx= context.WithValue(ctx, requestIDContextKey, requestId)

logInfo(ctx, "Get was called")

...

// il existe de nombreux paquets de journalisation que nous aurions pu utiliser, mais nous avons créé le nôtre pour plus de clarté dans cet exemple.

funclogInfo(ctxcontext.Context, message string) {

fmt.Println("info", message, ctx.Value(requestIDContextKey))

}

Pour voir le code complet, consultez mon application d'exemple sur GitHub à l'adresse https://github.com/paul-ferguson/the-go-context

Technologie
Modernisation des applications

Vous avez besoin d'aide pour créer votre prochaine application Go ?

Dernières réflexions

Explorez nos articles de blog et laissez-vous inspirer par les leaders d'opinion de nos entreprises.
Asset - Unlock the Value Thumbnail
Nuage

Transformer la planification financière : De SAP BPC à SAP Analytics Cloud (SAC)

Découvrez les avantages et les défis de la transition de SAP BPC à SAP Analytics Cloud (SAC) pour la planification financière.