Teilen über


Allgemeine Verwendungsmuster im Azure SDK für Go

Das Azure Core(azcore)-Paket im Azure SDK für Go implementiert mehrere Muster, die im gesamten SDK angewendet werden:

Paginierung (Methoden, die Sammlungen zurückgeben)

Viele Azure-Dienste geben Sammlungen von Elementen zurück. Da die Anzahl der Elemente groß sein kann, geben diese Clientmethoden einen Pager zurück, mit dem Ihre App jeweils eine Seite mit Ergebnissen verarbeiten kann. Diese Typen werden für verschiedene Kontexte einzeln definiert, teilen sich jedoch gemeinsame Merkmale, z. B. eine NextPage Methode.

Angenommen, es gibt eine ListWidgets Methode, die eine WidgetPager zurückgibt. Anschließend verwenden Sie das WidgetPager wie hier gezeigt:

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...
}

Zeitintensive Vorgänge

Einige Vorgänge in Azure können eine lange Zeit in Anspruch nehmen, von wenigen Sekunden bis zu einigen Tagen. Beispiele für solche Vorgänge sind das Kopieren von Daten aus einer Quell-URL in ein Speicher-Blob oder das Trainieren eines KI-Modells zum Erkennen von Formularen. Diese lang andauernden Vorgänge (LROs) eignen sich schlecht für den standardmäßigen HTTP-Fluss einer relativ schnellen Anforderung und Antwort.

Üblicherweise werden Methoden, die ein LRO starten, mit dem Präfix "Begin" versehen und geben einen Poller zurück. Der Poller wird verwendet, um den Dienst regelmäßig abzufragen, bis der Vorgang abgeschlossen ist.

Die folgenden Beispiele veranschaulichen verschiedene Muster für die Behandlung von LROs. Sie können auch mehr über den Quellcode "poller.go " im SDK erfahren.

Blockiere den Aufruf von PollUntilDone

PollUntilDone verarbeitet den gesamten Verlauf eines Abrufvorgangs, bis ein Endzustand erreicht ist. Anschließend wird die endgültige HTTP-Antwort für den Abfragevorgang mit dem Inhalt der Nutzlast in der respType Schnittstelle zurückgegeben.

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)

Benutzerdefinierte Abfrageschleife

Poll sendet eine Abrufanforderung an den Abrufendpunkt und gibt die Antwort oder einen Fehler zurück.

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)

Fortsetzen eines vorherigen Vorgangs

Extrahieren und speichern Sie das Fortsetzungstoken aus einem bestehenden Poller.

Um die Abfrage fortzusetzen, vielleicht in einem anderen Prozess oder auf einem anderen Computer, erstellen Sie eine neue PollerResponse Instanz und initialisieren Sie sie dann, indem Sie die Resume Methode aufrufen und das zuvor gespeicherte Fortsetzungstoken übergeben.

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)

HTTP-Pipelinefluss

Die verschiedenen SDK-Clients bieten eine Abstraktion über die REST-API von Azure, sodass Sie sich nicht mit Übertragungsmechanismen auf niedrigeren Ebenen über HTTP befassen müssen, und ermöglichen Code-Vervollständigung sowie Kompilierungszeit-Typsicherheit. Sie können jedoch die Transportmechanik (z. B. Wiederholungen und Protokollierung) anpassen .

Das SDK sendet HTTP-Anforderungen über eine HTTP-Pipeline. Die Pipeline beschreibt die Abfolge der Schritte, die für jede HTTP-Anfrage und -Antwort durchgeführt werden.

Die Pipeline setzt sich zusammen aus einem Transport und einer beliebigen Anzahl von Richtlinien.

  • Der Transport sendet die Anforderung an den Dienst und empfängt die Antwort.
  • Jede Richtlinie schließt eine bestimmte Aktion in der Pipeline ab.

Das folgende Diagramm veranschaulicht den Fluss einer Pipeline:

Diagramm, das den Fluss einer Pipeline zeigt.

Alle Clientpakete teilen ein Core-Paket mit dem Namen azcore. Dieses Paket erstellt die HTTP-Pipeline mit den geordneten Richtlinien, um sicherzustellen, dass sich alle Clientpakete konsistent verhalten.

Wenn eine HTTP-Anforderung gesendet wird, werden alle Richtlinien in der Reihenfolge ausgeführt, in der sie der Pipeline hinzugefügt wurden, bevor die Anforderung an den HTTP-Endpunkt gesendet wird. Diese Richtlinien fügen in der Regel Anforderungsheader hinzu oder protokollieren die ausgehende HTTP-Anforderung.

Nachdem der Azure-Dienst reagiert hat, werden alle Richtlinien in umgekehrter Reihenfolge ausgeführt, bevor die Antwort an Ihren Code gesendet wird. Die meisten Richtlinien ignorieren die Antwort, aber die Protokollierungsrichtlinie zeichnet die Antwort auf. Die Wiederholungsrichtlinie kann die Anforderung erneut anfordern, sodass Ihre App widerstandsfähiger für Netzwerkfehler ist.

Jede Richtlinie wird mit den erforderlichen Anforderungs- oder Antwortdaten sowie jedem notwendigen Kontext für deren Ausführung bereitgestellt. Die Richtlinie schließt den Vorgang mit den angegebenen Daten ab und übergibt dann die Kontrolle an die nächste Richtlinie in der Pipeline.

Standardmäßig erstellt jedes Clientpaket eine Pipeline, die für die Arbeit mit diesem bestimmten Azure-Dienst konfiguriert ist. Sie können auch eigene benutzerdefinierte Richtlinien definieren und sie beim Erstellen eines Clients in die HTTP-Pipeline einfügen.

Kernrichtlinien der HTTP-Pipeline

Das Core-Paket stellt drei HTTP-Richtlinien bereit, die Teil jeder Pipeline sind:

Benutzerdefinierte HTTP-Pipelinerichtlinien

Sie können Ihre eigene benutzerdefinierte Richtlinie definieren, um Funktionen über den Inhalt des Core-Pakets hinaus hinzuzufügen. Um beispielsweise zu sehen, wie sich Ihre App mit Netzwerk- oder Dienstfehlern befasst, können Sie eine Richtlinie erstellen, die Fehler eingibt, wenn Anforderungen während des Tests vorgenommen werden. Sie können auch eine Richtlinie erstellen, die das Verhalten eines Diensts zum Testen simuliert.

Um eine benutzerdefinierte HTTP-Richtlinie zu erstellen, definieren Sie Ihre eigene Struktur mit einer Do Methode, die die Policy Schnittstelle implementiert:

  1. Die Methode Ihrer Richtlinie Do sollte Vorgänge nach Bedarf für den eingehenden policy.Request ausführen. Beispiele für Vorgänge sind die Protokollierung von Ereignissen, das Einfügen eines Fehlers oder das Ändern der URL, der Abfrageparameter oder der Anforderungsheader.
  2. Die Do-Methode leitet die (geänderte) Anforderung an die nächste Richtlinie in der Pipeline weiter, indem sie die Next-Methode der Anforderung aufruft.
  3. Next gibt den http.Response und einen Fehler zurück. Ihre Richtlinie kann alle erforderlichen Vorgänge ausführen, z. B. die Protokollierung der Antwort/des Fehlers.
  4. Ihre Richtlinie muss eine Antwort und einen Fehler zurück an die vorherige Richtlinie innerhalb der Pipeline übermitteln.

Hinweis

Richtlinien müssen goroutine-sicher sein. Goroutine-Sicherheit ermöglicht es mehreren Goroutinen, gleichzeitig auf ein einzelnes Clientobjekt zuzugreifen. Es ist üblich, dass eine Richtlinie nach der Erstellung unveränderlich ist. Diese Unveränderlichkeit sorgt dafür, dass das Goroutin sicher ist.

Benutzerdefinierte Richtlinienvorlage

Der folgende Code kann als Ausgangspunkt zum Definieren einer benutzerdefinierten Richtlinie verwendet werden.

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
}

Benutzerdefinierter HTTP-Transport

Ein Transport sendet eine HTTP-Anforderung und gibt seine Antwort/den Fehler zurück. Die erste Richtlinie zur Behandlung der Anforderung ist auch die letzte Richtlinie, die die Antwort behandelt, bevor sie die Antwort/den Fehler zurück an die Richtlinien der Pipeline zurückgibt (in umgekehrter Reihenfolge). Die letzte Richtlinie in der Pipeline ruft den Transport auf.

Standardmäßig verwenden Clients die freigegebene http.Client aus der Go-Standardbibliothek.

Sie erstellen einen benutzerdefinierten zustandsbehafteten oder zustandslosen Transport in der gleichen Weise, wie Sie eine benutzerdefinierte Richtlinie erstellen. Im zustandsbehafteten Fall implementieren Sie die von der Do geerbte Methode . In beiden Fällen erhält Ihre Funktion oder Ihre Do-Methode erneut ein azcore.Request, gibt ein azCore.Response zurück und führt Aktionen in der gleichen Reihenfolge wie eine Richtlinie aus.

So löschen Sie ein JSON-Feld, wenn Sie einen Azure-Vorgang aufrufen

Vorgänge wie JSON-MERGE-PATCH das Senden eines JSON-Codes null , um anzugeben, dass ein Feld gelöscht werden soll (zusammen mit seinem Wert):

{
    "delete-me": null
}

Dieses Verhalten steht im Konflikt mit dem Standardmarsing des SDK, das als Methode zum Auflösen der Mehrdeutigkeit zwischen einem auszuschließenden Feld und seinem Nullwert angibt omitempty .

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

Im vorherigen Beispiel werden Name und Count als Zeiger auf Typ definiert, um zwischen einem fehlenden Wert (nil) und einem Nullwert (0) zu unterscheiden, die möglicherweise semantische Unterschiede aufweisen.

In einem HTTP PATCH-Vorgang wirken sich alle Felder mit dem Wert nil nicht auf den Wert in der Ressource des Servers aus. Wenn Sie das Feld eines Widgets Count aktualisieren, geben Sie den neuen Wert für Count an, während Name als nil bleibt.

Um die Anforderung, ein JSON zu senden, zu erfüllen, wird die Funktion null verwendet:

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

** Dieser Code setzt Countauf ein explizites JSONnull. Wenn die Anforderung an den Server gesendet wird, wird das Feld der Ressource Count gelöscht.

Siehe auch