Partager via


Modèles d’utilisation courants dans Azure SDK pour Go

Le package Azure Core (azcore) dans Azure SDK pour Go implémente plusieurs modèles qui sont appliqués dans le SDK :

Pagination (méthodes qui retournent des collections)

De nombreux services Azure retournent des collections d’éléments. Comme le nombre d’éléments peut être élevé, ces méthodes clientes retournent un Pager, qui permet à votre application de traiter une page de résultats à la fois. Ces types sont définis individuellement pour divers contextes, mais partagent des caractéristiques communes, comme la méthode NextPage.

Par exemple, supposons qu’une méthode ListWidgets retourne un WidgetPager. Vous utilisez WidgetPager comme illustré ici :

func (c *WidgetClient) ListWidgets(options *ListWidgetOptions) WidgetPager {
    // ...
}

pager := client.ListWidgets(options)

for pager.NextPage(ctx) {
    for _, w := range pager.PageResponse().Widgets {
        process(w)
    }
}

if pager.Err() != nil {
    // Handle error...
}

Opérations de longue durée

L’exécution de certaines opérations sur Azure peut durer longtemps (de quelques secondes à quelques jours). Exemples de ce type d’opérations : copie de données d’une URL source vers un blob de stockage ou entraînement d’un modèle IA pour reconnaître des formulaires. Ces opérations longues (LRO) mal adaptées au flux HTTP standard d’une requête et d’une réponse relativement rapides.

Par convention, les méthodes qui démarrent une opération longue sont préfixées avec « Begin » et retournent un Poller. Le « Poller » est utilisé pour interroger régulièrement le service jusqu’à la fin de l’opération.

Les exemples suivants illustrent différents modèles de gestion des opérations longues. Pour en savoir plus, vous pouvez consulter le code source poller.go dans le SDK.

Blocage de l’appel sur PollUntilDone

PollUntilDone gère la totalité de l’étendue d’une opération d’interrogation jusqu’à ce qu’un état terminal soit atteint. Elle retourne ensuite la réponse HTTP finale pour l’opération d’interrogation avec le contenu de la charge utile dans l’interface respType .

resp, err := client.BeginCreate(context.Background(), "blue_widget", nil)

if err != nil {
    // Handle error...
}

w, err = resp.PollUntilDone(context.Background(), nil)

if err != nil {
    // Handle error...
}

process(w)

Boucle d’interrogation personnalisée

Poll envoie une demande d’interrogation au point de terminaison d’interrogation et retourne la réponse ou une erreur.

resp, err := client.BeginCreate(context.Background(), "green_widget")

if err != nil {
    // Handle error...
}

poller := resp.Poller

for {
    resp, err := poller.Poll(context.Background())

    if err != nil {
        // Handle error...
    }

    if poller.Done() {
        break
    }

    // Do other work while waiting.
}

w, err := poller.FinalResponse(ctx)

if err != nil {
    // Handle error...
}

process(w)

Reprendre à partir d’une opération précédente

Extrayez le jeton de reprise d’un Poller existant et enregistrez-le.

Pour reprendre l’interrogation, peut-être dans un autre processus ou sur un autre ordinateur, créez une instance PollerResponse, puis initialisez-la en appelant sa méthode Resume avec le jeton de reprise que vous venez d’enregistrer.

poller := resp.Poller
tk, err := poller.ResumeToken()

if err != nil {
    // Handle error...
}

resp = WidgetPollerResponse()

// Resume takes the resume token as an argument.
err := resp.Resume(tk, ...)

if err != nil {
    // Handle error...
}

for {
    resp, err := poller.Poll(context.Background())

    if err != nil {
        // Handle error...
    }

    if poller.Done() {
        break
    }

    // Do other work while waiting.
}

w, err := poller.FinalResponse(ctx)

if err != nil {
    // Handle error...
}

process(w)

Flux de pipeline HTTP

Les différents clients du KIT de développement logiciel (SDK) fournissent une abstraction sur l’API REST d’Azure pour permettre la saisie semi-automatique du code et la sécurité des types au moment de la compilation afin que vous n’ayez pas à gérer les mécanismes de transport de niveau inférieur via HTTP. Toutefois, vous pouvez personnaliser les mécanismes de transport (comme les nouvelles tentatives et la journalisation).

Le SDK envoie des requêtes HTTP en utilisant un pipeline HTTP. Un pipeline décrit la séquence des étapes effectuées pour chaque boucle requête-réponse HTTP.

Le pipeline est composé d’un transport avec une ou plusieurs stratégies :

  • Le transport envoie la demande au service et reçoit la réponse.
  • Chaque stratégie effectue une action spécifique dans le pipeline.

Le diagramme suivant illustre le flux d’un pipeline :

Diagramme qui montre le flux d’un pipeline.

Tous les packages clients partagent un package Core appelé azcore. Ce package construit le pipeline HTTP avec son jeu ordonné de stratégies garantissant que tous les packages clients ont un comportement cohérent.

Quand une requête HTTP est envoyée, toutes les stratégies s’exécutent dans l’ordre dans lequel elles ont été ajoutées au pipeline avant que la requête soit envoyée au point de terminaison HTTP. En général, ces stratégies ajoutent des en-têtes de demande ou journalisent la requête HTTP sortante.

Quand le service Azure répond, toutes les stratégies s’exécutent dans l’ordre inverse avant de retourner la réponse à votre code. La plupart des stratégies ignorent la réponse, mais la stratégie de journalisation enregistre la réponse. La stratégie de nouvelle tentative peut réécrire la requête, ce qui rend votre application plus résiliente aux défaillances réseau.

Chaque stratégie est fournie avec les données de requête ou de réponse nécessaires, ainsi que tout le contexte nécessaire à son exécution. La stratégie effectue son opération avec les données spécifiées et passe le contrôle à la stratégie suivante dans le pipeline.

Par défaut, chaque package client crée un pipeline configuré pour fonctionner avec ce service Azure spécifique. Vous pouvez également définir et insérer vos propres stratégies personnalisées dans le pipeline HTTP quand vous créez un client.

Stratégies de base de pipeline HTTP

Le package de base fournit trois stratégies HTTP intégrées à chaque pipeline :

Stratégies de pipeline HTTP personnalisées

Vous pouvez définir votre propre stratégie personnalisée pour ajouter des fonctionnalités au-delà du contenu du package Core. Par exemple, pour voir comment votre application gère les pannes de réseau ou de service, vous pourriez créer une stratégie qui injecte une erreur pendant que des demandes sont effectuées lors des tests. Vous pouvez aussi créer une stratégie qui simule le comportement d’un service à des fins de test.

Pour créer une stratégie HTTP personnalisée, définissez votre propre structure avec une méthode Do qui implémente l’interface Policy :

  1. La méthode Do de votre stratégie doit effectuer les opérations nécessaires sur le policy.Request entrant. Les exemples d’opérations peuvent être la journalisation, l’injection d’une défaillance ou la modification d’une URL, des paramètres ou des en-têtes de la requête.
  2. La méthode Do transfère la demande (modifiée) à la stratégie suivante dans le pipeline en appelant la méthode Next de la requête.
  3. Next retourne http.Response et une erreur. Votre stratégie peut effectuer toute opération nécessaire, comme la journalisation de la réponse/l’erreur.
  4. Votre stratégie doit renvoyer une réponse et une erreur à la stratégie précédente dans le pipeline.

Remarque

Les stratégies doivent être « goroutine-safe ». La sécurité des goroutines permet à plusieurs goroutines d’accéder simultanément à un même objet client. Il est courant qu’une stratégie soit immuable après sa création. Cette immuabilité garantit que la goroutine est sûre.

Modèle de stratégie personnalisée

Le code suivant peut être utilisé comme point de départ pour définir une stratégie personnalisée.

type MyPolicy struct {
    LogPrefix string
}

func (m *MyPolicy) Do(req *policy.Request) (*http.Response, error) {
	// Mutate/process request.
	start := time.Now()
	// Forward the request to the next policy in the pipeline.
	res, err := req.Next()
	// Mutate/process response.
	// Return the response & error back to the previous policy in the pipeline.
	record := struct {
		Policy   string
		URL      string
		Duration time.Duration
	}{
		Policy:   "MyPolicy",
		URL:      req.Raw().URL.RequestURI(),
		Duration: time.Duration(time.Since(start).Milliseconds()),
	}
	b, _ := json.Marshal(record)
	log.Printf("%s %s\n", m.LogPrefix, b)
	return res, err
}

func ListResourcesWithPolicy(subscriptionID string) error {
	cred, err := azidentity.NewDefaultAzureCredential(nil)
	if err != nil {
		return err
	}

	mp := &MyPolicy{
		LogPrefix: "[MyPolicy]",
	}
	options := &arm.ConnectionOptions{}
	options.PerCallPolicies = []policy.Policy{mp}
	options.Retry = policy.RetryOptions{
		RetryDelay: 20 * time.Millisecond,
	}

	con := arm.NewDefaultConnection(cred, options)
	if err != nil {
		return err
	}

	client := armresources.NewResourcesClient(con, subscriptionID)
	pager := client.List(nil)
	for pager.NextPage(context.Background()) {
		if err := pager.Err(); err != nil {
			log.Fatalf("failed to advance page: %v", err)
		}
		for _, r := range pager.PageResponse().ResourceListResult.Value {
			printJSON(r)
		}
	}
	return nil
}

Transport HTTP personnalisé

Un transport envoie une requête HTTP et retourne sa réponse/erreur. La première stratégie à gérer la requête est également la dernière stratégie qui gère la réponse avant de renvoyer la réponse/erreur aux stratégies du pipeline (dans l’ordre inverse). La dernière stratégie du pipeline appelle le transport.

Par défaut, les clients utilisent l’élément partagé http.Client de la bibliothèque standard de Go.

Vous créez un transport personnalisé avec état ou sans état de la même manière que vous créez une stratégie personnalisée. Pour un transport avec état, vous implémentez la méthode Do héritée de l’interface Transporter. Dans les deux cas, votre fonction ou votre méthode Do reçoit à nouveau un azcore.Request et retourne un azCore.Response avant d’effectuer des actions dans le même ordre qu’une stratégie.

Comment supprimer un champ JSON quand vous appelez une opération Azure

Les opérations de type JSON-MERGE-PATCH envoient un JSON null pour indiquer qu’un champ doit être supprimé (avec sa valeur) :

{
    "delete-me": null
}

Ce comportement est en conflit avec le marshaling par défaut du SDK qui spécifie omitempty comme moyen de résoudre l’ambiguïté entre un champ à exclure et sa valeur zéro.

type Widget struct {
    Name *string `json:",omitempty"`
    Count *int `json:",omitempty"`
}

Dans l’exemple précédent, Name et Count sont définis comme pointeur vers type pour lever l’ambiguïté entre une valeur manquante (nil) et une valeur zéro (0) qui peuvent avoir des différences sémantiques.

Dans une opération HTTP PATCH, tous les champs dont la valeur nil n’affecte pas la valeur dans la ressource du serveur. Quand vous mettez à jour le champ Count d’un widget, spécifiez la nouvelle valeur de Count et laissez nil pour Name.

Pour répondre à l’exigence consistant à envoyer un JSON null, vous utilisez la fonction NullValue :

w := Widget{
    Count: azcore.NullValue(0).(*int),
}

Ce code définit Count sur un JSON null explicite. Lorsque la demande est envoyée au serveur, le champ de Count la ressource est supprimé.

Voir aussi