Gängige Verwendungsmuster im Azure SDK für Go
Mit dem Azure Core-Paket (azcore
) im Azure SDK für Go werden mehrere Muster implementiert, die im gesamten SDK angewendet werden:
- HTTP-Pipelinefluss: Dies ist der zugrunde liegende HTTP-Mechanismus, der von den Clientbibliotheken des SDK genutzt wird.
- Paginierung (Methoden, die Sammlungen zurückgeben)
- Zeitintensive Vorgänge
Paginierung (Methoden, die Sammlungen zurückgeben)
Von vielen Azure-Diensten werden Sammlungen mit Elementen zurückgegeben. Da die Anzahl von Elementen sehr hoch sein kann, wird von diesen Clientmethoden ein Pager zurückgegeben, der es Ihrer App ermöglicht, jeweils nur eine Seite mit Ergebnissen zu verarbeiten. Diese Typen werden für unterschiedliche Kontexte einzeln definiert, aber sie verfügen über gemeinsame Merkmale, z. B. eine NextPage
-Methode.
Angenommen, die ListWidgets
-Methode gibt das WidgetPager
-Element zurück. Sie würden das WidgetPager
-Element dann wie hier gezeigt verwenden:
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
Bei einigen Vorgängen in Azure kann es sehr lange dauern, bis diese abgeschlossen sind. Die Dauer reicht hierbei von einigen Sekunden bis zu mehreren Tagen. Beispiele für Vorgänge dieser Art sind das Kopieren von Daten aus einer Quell-URL in ein Speicherblob oder das Trainieren eines KI-Modells für die Erkennung von Formen. Diese lang andauernden Vorgänge (LROs) eignen sich schlecht für den standardmäßigen HTTP-Fluss einer relativ schnellen Anforderung und Antwort.
Methoden, bei denen ein zeitintensiver Vorgang gestartet wird, verfügen standardmäßig über das Präfix „Begin“, und es wird eine Poller-Komponente zurückgegeben. Der Poller wird verwendet, um den Dienst in regelmäßigen Abständen abzufragen, bis der Vorgang abgeschlossen ist.
In den folgenden Beispielen werden verschiedene Muster für die Verarbeitung zeitintensiver Vorgänge veranschaulicht. Weitere Informationen finden Sie auch im Quellcode unter poller.go für das SDK.
Blockieren des Aufrufs von „PollUntilDone“
Mit PollUntilDone
wird der gesamte Umfang des Abrufvorgangs bis zur Erreichung eines Endzustands verarbeitet. Anschließend wird die endgültige HTTP-Antwort für den Abrufvorgang 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
Bei Poll
wird eine Abrufanforderung an den Abrufendpunkt gesendet und die Antwort oder ein Fehler zurückgegeben.
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 aus einem vorherigen Vorgang
Extrahieren Sie das Fortsetzungstoken aus einer vorhandenen Poller-Komponente, und speichern Sie es.
Erstellen Sie zum Fortsetzen des Abrufvorgangs (z. B. in einem anderen Prozess oder auf einem anderen Computer) eine neue PollerResponse
-Instanz, und initialisieren Sie sie. Rufen Sie hierzu die zugehörige Resume
-Methode auf, und übergeben Sie das zuvor gespeicherte Fortsetzungstoken.
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, um codevervollständigen und Kompilierungszeittypsicherheit zu ermöglichen, sodass Sie sich nicht mit Transportmechaniken auf niedrigerer Ebene über HTTP befassen müssen. Sie können jedoch die Transportmechanik (z. B. Wiederholungen und Protokollierung) anpassen .
Das SDK sendet HTTP-Anforderungen über eine HTTP-Pipeline. Mit der Pipeline wird die Schrittfolge beschrieben, die bei jedem Roundtrip vom Typ „HTTP-Anforderung/Antwort“ ausgeführt wird.
Die Pipeline besteht aus einer Transportkomponente und einer beliebigen Anzahl von Richtlinien:
- Die Transport-Komponente sendet die Anforderung an den Dienst und empfängt die Antwort.
- Von jeder Richtlinie wird innerhalb der Pipeline eine bestimmte Aktion durchgeführt.
Der Datenfluss einer Pipeline ist in der folgenden Abbildung dargestellt:
Für alle Clientpakete wird ein gemeinsames Core-Paket mit dem Namen azcore
genutzt. Mit diesem Paket wird die HTTP-Pipeline mit den sortierten Richtlinien erstellt, um dafür zu sorgen, dass sich alle Clientpakete auf einheitliche Weise verhalten.
Beim Senden einer HTTP-Anforderung 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. Bei diesen Richtlinien werden häufig Anforderungsheader hinzugefügt oder ausgehende HTTP-Anforderungen protokolliert.
Nachdem der Azure-Dienst geantwortet hat, werden alle Richtlinien in umgekehrter Reihenfolge ausgeführt, bevor die Antwort an Ihren Code zurückgegeben wird. Von den meisten Richtlinien wird die Antwort ignoriert, 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- bzw. Antwortdaten sowie mit dem für die Ausführung der Richtlinie benötigten Kontext bereitgestellt. Die Richtlinie nutzt für ihren Vorgang die bereitgestellten Daten und übergibt die Kontrolle dann an die nächste Richtlinie in der Pipeline.
Standardmäßig wird von jedem Clientpaket eine Pipeline erstellt, die für die Arbeit mit dem jeweiligen Azure-Dienst konfiguriert ist. Sie können beim Erstellen eines Clients auch Ihre eigenen benutzerdefinierten Richtlinien definieren und in die HTTP-Pipeline einfügen.
Core-Richtlinien für HTTP-Pipelines
Das Core-Paket enthält drei HTTP-Richtlinien, 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. Beispielsweise können Sie eine Richtlinie erstellen, mit der beim Senden von Anforderungen während Tests Fehler eingeschleust werden, damit Sie sehen, wie Ihre App Netzwerk- oder Dienstausfälle verarbeitet. Sie können zu Testzwecken auch eine Richtlinie erstellen, mit der das Verhalten eines Diensts simuliert wird.
Definieren Sie zum Erstellen einer benutzerdefinierten HTTP-Richtlinie Ihre eigene Struktur mit einer Do
-Methode, die die Policy
-Schnittstelle implementiert:
- Die
Do
-Methode Ihrer Richtlinie sollte bei Bedarf Vorgänge für die eingehende Anforderung (policy.Request
) ausführen. Beispiele für Vorgänge sind das Führen von Protokollen, das Einschleusen eines Fehlers oder das Ändern der URL, Abfrageparameter oder Anforderungsheader für die Anforderung. - Die
Do
-Methode leitet die (geänderte) Anforderung an die nächste Richtlinie in der Pipeline weiter, indem sie dieNext
-Methode der Anforderung aufruft. - Von der
Next
-Methode werden dashttp.Response
-Element und ein Fehler zurückgegeben. Mit Ihrer Richtlinie kann ein beliebiger erforderlicher Vorgang durchgeführt werden, z. B. die Protokollierung der Antwort bzw. des Fehlers. - Von Ihrer Richtlinie müssen eine Antwort und ein Fehler an die vorherige Richtlinie in der Pipeline zurückgegeben werden.
Hinweis
Die Richtlinien müssen für Go-Routinen geeignet sein. Durch die Sicherheit der Go-Routine können mehrere Go-Routinen gleichzeitig auf ein Clientobjekt zugreifen. Es kommt häufig vor, dass eine Richtlinie nach ihrer Erstellung unveränderlich ist. So wird für die Sicherheit der Go-Routine gesorgt.
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
Mit einer „Transport“-Komponente wird eine HTTP-Anforderung gesendet und die zugehörige Antwort bzw. der Fehler zurückgegeben. 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.
Für Clients wird standardmäßig das freigegebene http.Client
-Element aus der Go-Standardbibliothek genutzt.
Sie gehen beim Erstellen einer benutzerdefinierten zustandsbehafteten oder zustandslosen Transportkomponente genauso wie bei einer benutzerdefinierten Richtlinie vor. Bei der zustandsbehafteten Komponente implementieren Sie die Do
-Methode, die von der Transporter-Schnittstelle geerbt wird. In beiden Fällen empfängt Ihre Funktion bzw. Do
-Methode wieder ein azcore.Request
-Element und gibt ein azCore.Response
-Element zurück. Anschließend werden die Aktionen in der gleichen Reihenfolge wie bei einer Richtlinie durchgeführt.
Löschen eines JSON-Felds beim Aufrufen eines Azure-Vorgangs
Bei Vorgängen wie JSON-MERGE-PATCH
wird das JSON-Element null
gesendet, um anzugeben, dass ein Feld (einschließlich des zugehörigen Werts) gelöscht werden sollte:
{
"delete-me": null
}
Dieses Verhalten steht in Konflikt mit dem Standardmarshalling des SDK, bei dem omitempty
als Mittel zum Beheben der Mehrdeutigkeit zwischen einem auszuschließenden Feld und seinem Nullwert angegeben wird.
type Widget struct {
Name *string `json:",omitempty"`
Count *int `json:",omitempty"`
}
Im obigen Beispiel werden Name
und Count
als Zeiger auf den Typ definiert, um die Unterscheidung zwischen einem fehlenden Wert (nil
) und einem Nullwert (0) sicherzustellen, da hierfür ggf. semantische Unterschiede gelten.
In einem HTTP PATCH-Vorgang wirken sich alle Felder mit dem Wert nil
nicht auf den Wert in der Ressource des Servers aus. Geben Sie beim Aktualisieren des Felds Count
eines Widgets den neuen Wert für Count
an, und behalten Sie für Name
die Einstellung nil
bei.
Zur Erfüllung der Anforderung zum Senden des JSON-Elements null
wird die Funktion NullValue
verwendet:
w := Widget{
Count: azcore.NullValue(0).(*int),
}
Mit diesem Code wird Count
auf das explizite JSON-Element null
festgelegt. Wenn die Anforderung an den Server gesendet wird, wird das Feld der Ressource Count
gelöscht.