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 :
- Le flux de pipeline HTTP, qui est le mécanisme HTTP sous-jacent utilisé par les bibliothèques de client du SDK.
- Pagination (méthodes qui retournent des collections).
- Opérations longues.
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 :
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
:
- La méthode
Do
de votre stratégie doit effectuer les opérations nécessaires sur lepolicy.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. - La méthode
Do
transfère la demande (modifiée) à la stratégie suivante dans le pipeline en appelant la méthodeNext
de la requête. Next
retournehttp.Response
et une erreur. Votre stratégie peut effectuer toute opération nécessaire, comme la journalisation de la réponse/l’erreur.- 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é.