Share via


Modelli di utilizzo comuni in Azure SDK per Go

Il pacchetto Azure Core (azcore) in Azure SDK for Go implementa diversi modelli applicati in tutto l'SDK:

Paginazione (metodi che restituiscono raccolte)

Molti servizi di Azure restituiscono raccolte di elementi. Poiché il numero di elementi può essere elevato, questi metodi client restituiscono un pager, che consente all'app di elaborare una pagina di risultati alla volta. Questi tipi sono definiti singolarmente per vari contesti, ma condividono caratteristiche comuni, ad esempio un NextPage metodo.

Si supponga, ad esempio, che sia presente un ListWidgets metodo che restituisce un oggetto WidgetPager. Si userà quindi come WidgetPager illustrato di seguito:

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

Operazioni a esecuzione prolungata

Il completamento di alcune operazioni in Azure può richiedere molto tempo, da pochi secondi a pochi giorni. Esempi di queste operazioni includono la copia di dati da un URL di origine a un BLOB di archiviazione o il training di un modello di intelligenza artificiale per riconoscere i moduli. Queste operazioni a esecuzione prolungata (LRO) non prestano bene al flusso HTTP standard di una richiesta e una risposta relativamente rapida.

Per convenzione, i metodi che avviano un LRO sono preceduti da "Begin" e restituiscono un poller. Il poller viene usato per eseguire periodicamente il polling del servizio fino al termine dell'operazione.

Gli esempi seguenti illustrano vari modelli per la gestione di oggetti LRO. Per altre informazioni, vedere il codice sorgente poller.go nell'SDK.

Blocco della chiamata a PollUntilDone

PollUntilDone gestisce l'intero intervallo di un'operazione di polling finché non viene raggiunto uno stato terminale. Restituisce quindi la risposta HTTP finale per l'operazione di polling con il contenuto del payload nell'interfaccia respType fornita.

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)

Ciclo di polling personalizzato

Poll invia una richiesta di polling all'endpoint di polling e restituisce la risposta o un errore.

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)

Riprendere da un'operazione precedente

Estrarre e salvare il token di ripresa da un poller esistente.

Per riprendere il polling, magari in un altro processo o in un altro computer, creare una nuova PollerResponse istanza e quindi inizializzarla chiamando il Resume relativo metodo, passando il token di ripresa salvato in precedenza.

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)

Flusso della pipeline HTTP

I vari client forniscono un'astrazione sull'API HTTP di un servizio di Azure per abilitare il completamento del codice e la sicurezza dei tipi in fase di compilazione. Quindi non è necessario gestire i meccanismi di trasporto di livello inferiore. Ma è possibile personalizzare i meccanismi di trasporto (ad esempio tentativi e registrazione).

L'SDK effettua richieste HTTP tramite una pipeline HTTP. La pipeline descrive la sequenza di passaggi eseguiti per ogni round trip di richiesta-risposta HTTP.

La pipeline è costituita da un trasporto insieme a un numero qualsiasi di criteri:

  • Il trasporto invia la richiesta al servizio e riceve la risposta.
  • Ogni criterio completa un'azione specifica nella pipeline.

Questo diagramma illustra il flusso di una pipeline:

Diagram that shows the flow of a pipeline.

Tutti i pacchetti client condividono un pacchetto Core denominato azcore. Questo pacchetto costruisce la pipeline HTTP con il set ordinato di criteri, assicurando che tutti i pacchetti client si comportino in modo coerente.

  • Quando viene inviata una richiesta HTTP, tutti i criteri vengono eseguiti nell'ordine in cui sono stati aggiunti alla pipeline prima che la richiesta venga inviata all'endpoint HTTP. Questi criteri in genere aggiungono intestazioni di richiesta o registrano la richiesta HTTP in uscita.
  • Dopo la risposta del servizio di Azure, tutti i criteri vengono eseguiti nell'ordine inverso prima che la risposta restituisca il codice. La maggior parte dei criteri ignora la risposta, ma i criteri di registrazione registrano la risposta. I criteri di ripetizione dei tentativi potrebbero generare nuovamente la richiesta, rendendo l'app più resiliente agli errori di rete.

Ogni criterio viene fornito con i dati di richiesta o risposta necessari, insieme a qualsiasi contesto necessario per l'esecuzione dei criteri. Il criterio completa l'operazione con i dati specificati e quindi passa il controllo ai criteri successivi nella pipeline.

Per impostazione predefinita, ogni pacchetto client crea una pipeline configurata per l'uso con tale servizio di Azure specifico. È anche possibile definire criteri personalizzati e inserirli nella pipeline HTTP quando si crea un client.

Criteri della pipeline HTTP di base

Il pacchetto Core fornisce tre criteri HTTP che fanno parte di ogni pipeline:

Criteri di pipeline HTTP personalizzati

È possibile definire criteri personalizzati per aggiungere funzionalità oltre a quanto incluso nel pacchetto Core. Ad esempio, per vedere in che modo l'app gestisce gli errori di rete o di servizio, è possibile creare un criterio che inserisce errori quando vengono effettuate richieste durante i test. In alternativa, è possibile creare un criterio che simula il comportamento di un servizio per il test.

Per creare un criterio HTTP personalizzato, definire la propria struttura con un Do metodo che implementa l'interfaccia Policy :

  1. Il metodo del Do criterio deve eseguire operazioni in base alle esigenze nell'oggetto in ingresso policy.Request. Esempi di operazioni includono la registrazione, l'inserimento di un errore o la modifica di un URL della richiesta, parametri di query o intestazioni di richiesta.
  2. Il Do metodo inoltra la richiesta (modificata) ai criteri successivi nella pipeline chiamando il metodo della Next richiesta.
  3. Next restituisce e http.Response un errore. I criteri possono eseguire qualsiasi operazione necessaria, ad esempio registrare la risposta o l'errore.
  4. I criteri devono restituire una risposta e un errore ai criteri precedenti nella pipeline.

Nota

I criteri devono essere sicuri per goroutine. La sicurezza goroutine consente a più gooutine di accedere contemporaneamente a un singolo oggetto client. È comune che un criterio non sia modificabile dopo la creazione. Questa immutabilità garantisce che la goroutine sia sicura.

La sezione seguente illustra come definire un criterio personalizzato.

Modello di criteri

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
}

Trasporto HTTP personalizzato

Un trasporto invia una richiesta HTTP e restituisce la risposta/errore. Il trasporto viene richiamato dall'ultimo criterio nella pipeline. È il primo criterio per gestire la risposta prima di restituire la risposta/errore ai criteri della pipeline (in ordine inverso).

Per impostazione predefinita, i client usano la condivisione http.Client dalla libreria standard di Go.

Si crea un trasporto personalizzato con stato o senza stato nello stesso modo in cui si creano criteri personalizzati. Nel caso con stato, si implementa il Do metodo ereditato dall'interfaccia Transporter . In entrambi i casi, la funzione o Do il metodo riceve di nuovo un azcore.Requestoggetto , restituisce un azCore.Responseoggetto ed esegue azioni nello stesso ordine di un criterio.

Come eliminare un campo JSON quando si richiama un'operazione di Azure

Operazioni come JSON-MERGE-PATCH l'invio di un codice JSON null per indicare che un campo deve essere eliminato (insieme al relativo valore):

{
    "delete-me": null
}

Questo comportamento è in conflitto con il marshalling predefinito dell'SDK che specifica omitempty come modo per risolvere l'ambiguità tra un campo da escludere e il relativo valore zero.

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

Nell'esempio Name precedente e Count sono definiti come puntatore a tipo per evitare ambiguità tra un valore mancante (nil) e un valore zero (0), che potrebbero avere differenze semantiche.

In un'operazione HTTP PATCH qualsiasi campo il cui valore nil non influisce sul valore nella risorsa del server. Quando si aggiorna il campo di Count un widget, specificare il nuovo valore per Count, lasciando Name come nil.

Per soddisfare i requisiti per l'invio di un codice JSON null, viene usata la NullValue funzione :

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

Questo codice imposta Count su un codice JSON nullesplicito. Quando la richiesta viene inviata al server, il campo della Count risorsa verrà eliminato.

Vedi anche