Patrones de uso comunes de Azure SDK para Go
El paquete Azure Core (azcore
) de Azure SDK para Go implementa varios patrones que se aplican en todo el SDK:
- El flujo de canalización HTTP, que es el mecanismo HTTP subyacente que usan las bibliotecas cliente del SDK.
- Paginación (métodos que devuelven colecciones)
- Operaciones de larga duración (LRO)
Paginación (métodos que devuelven colecciones)
Muchos servicios de Azure devuelven colecciones de elementos. Dado que el número de elementos puede ser enorme, estos métodos de cliente devuelven un elemento Pager (paginador), lo que permite a la aplicación procesar una página de resultados a la vez. Estos tipos se definen individualmente para diversos contextos, pero comparten características comunes como el método NextPage
.
Por ejemplo, supongamos que hay un método ListWidgets
que devuelve un elemento WidgetPager
. Después, usará el elemento WidgetPager
tal como se muestra en el código siguiente:
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...
}
Operaciones de larga duración
Algunas operaciones en Azure pueden tardar mucho tiempo en completarse, desde unos segundos hasta unos días. Entre los ejemplos de estas operaciones, se incluyen la copia de datos desde una dirección URL de origen a un blob de almacenamiento o el entrenamiento de un modelo de IA para reconocer formularios. Estas operaciones de larga duración (LRO) son poco adecuadas para el flujo HTTP estándar de una solicitud y respuesta relativamente rápidas.
Por convención, los métodos que inician una LRO tienen el prefijo "Begin" y devuelven un elemento Poller. El elemento Poller se usa para sondear periódicamente el servicio hasta que se finaliza la operación.
En los ejemplos siguientes, se muestran varios patrones para controlar las LRO. También puede obtener información en el código fuente de poller.go en el SDK.
Bloqueo de la llamada a PollUntilDone
PollUntilDone
controla todo el intervalo de la operación de sondeo hasta que se alcanza un estado terminal. A continuación, devuelve la respuesta HTTP final para la operación de sondeo con el contenido de la carga en la respType
interfaz.
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)
Bucle de sondeo personalizado
Poll
envía una solicitud de sondeo al punto de conexión de sondeo y devuelve la respuesta o un error.
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)
Reanudación de una operación anterior
Extraiga y guarde el token de reanudación de un sondeador existente.
Para reanudar el sondeo, quizás en otro proceso o equipo, cree una nueva instancia de PollerResponse
y, a continuación, inicialícela mediante una llamada a su método Resume
, pasando el token de reanudación guardado previamente.
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)
Flujo de canalización HTTP
Los distintos clientes del SDK proporcionan una abstracción sobre la API REST de Azure para habilitar la finalización del código y la seguridad de tipos en tiempo de compilación, por lo que no es necesario tratar con la mecánica de transporte de nivel inferior a través de HTTP. Sin embargo, puede personalizar la mecánica de transporte (como reintentos y registro).
El SDK realiza solicitudes HTTP mediante una canalización HTTP. La canalización describe la secuencia de pasos que se realizan para el recorrido de ida y vuelta de cada solicitud-respuesta HTTP.
La canalización se compone de un transporte junto con cualquier número de directivas:
- El transporte envía la solicitud al servicio y recibe la respuesta.
- Cada directiva realiza una acción específica dentro de la canalización.
En el diagrama siguiente, se muestra el flujo de una canalización:
Todos los paquetes de cliente comparten un paquete Core llamado azcore
. Este paquete construye la canalización HTTP con su conjunto ordenado de directivas, lo que garantiza que todos los paquetes de cliente se comporten de forma coherente.
Cuando se envía una solicitud HTTP, todas las directivas se ejecutan en el orden en que se agregaron a la canalización antes de enviar la solicitud al punto de conexión HTTP. Estas directivas suelen agregar encabezados de solicitud o registrar la solicitud HTTP saliente.
Después de que el servicio de Azure responda, todas las directivas se ejecutan en orden inverso antes de que la respuesta vuelva al código. La mayoría de las directivas ignoran la respuesta, pero la política de registro registra la respuesta. La directiva de reintento podría volver a emitir la solicitud, lo que hace que la aplicación sea más resistente a los errores de red.
Cada directiva se proporciona con los datos de solicitud o respuesta necesarios, junto con cualquier contexto necesario para ejecutar la directiva. La directiva completa su operación con los datos especificados y luego pasa el control a la siguiente directiva de la canalización.
De manera predeterminada, cada paquete de cliente crea una canalización configurada para trabajar con ese servicio de Azure específico. También puede definir sus propias directivas personalizadas en la canalización HTTP al crear un cliente.
Directivas de canalización HTTP del paquete Core
El paquete Core proporciona tres directivas HTTP que forman parte de cada canalización:
Directivas de canalización HTTP personalizadas
Puede definir su propia directiva personalizada para agregar funcionalidades más allá del contenido del paquete Core. Por ejemplo, para ver cómo trata la aplicación los errores de red o del servicio, puede crear una directiva que inserta errores al realizar solicitudes durante las pruebas. O bien, puede crear una directiva que simula el comportamiento de un servicio para las pruebas.
Para crear una directiva HTTP personalizada, defina su propia estructura con un método Do
que implemente la interfaz Policy
.
- El método
Do
de la directiva debe realizar operaciones según sea necesario en elpolicy.Request
entrante. Entre los ejemplos de operaciones se incluyen el registro, la inserción de un error o la modificación de cualquiera de las direcciones URL, parámetros de consulta o encabezados de solicitud de la solicitud. - El método
Do
reenvía la solicitud (modificada) a la siguiente directiva de la canalización mediante una llamada al métodoNext
de la solicitud. Next
devuelve el elementohttp.Response
y un error. La directiva puede realizar cualquier operación necesaria, como registrar la respuesta o el error.- La directiva debe devolver una respuesta y un error a la directiva anterior de la canalización.
Nota:
Las directivas deben ser seguras para la rutina de Go. La seguridad para rutinas de Go permite que varias rutinas de Go accedan simultáneamente a un único objeto de cliente. Es habitual que una directiva sea inmutable después de crearla. Esta inmutabilidad garantiza que la rutina de Go sea segura.
Plantilla de directiva personalizada
El código siguiente se puede usar como punto de partida para definir una directiva personalizada.
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
}
Transporte HTTP personalizado
Un transporte envía una solicitud HTTP y devuelve su respuesta y error. La primera directiva para controlar la solicitud es también la última directiva que controla la respuesta antes de devolver la respuesta o el error a las directivas de la canalización (en orden inverso). La última directiva de la canalización invoca el transporte.
De manera predeterminada, los clientes usan el elemento http.Client
compartido de la biblioteca estándar de Go.
Puede crear un transporte personalizado con estado o sin estado de la misma manera que crearía una directiva personalizada. En el caso de un transporte con estado, se implementa el método Do
heredado de la interfaz de Transporter. En ambos casos, la función o el método Do
recibe de nuevo un elemento azcore.Request
, devuelve un elemento azCore.Response
y realiza acciones en el mismo orden que una directiva.
Eliminación de un campo JSON al invocar una operación de Azure
Las operaciones como JSON-MERGE-PATCH
envían un elemento JSON null
para indicar que se debe eliminar un campo (junto con su valor):
{
"delete-me": null
}
Este comportamiento entra en conflicto con la serialización predeterminada del SDK, que especifica omitempty
como forma de resolver la ambigüedad entre un campo que se va a excluir y su valor cero.
type Widget struct {
Name *string `json:",omitempty"`
Count *int `json:",omitempty"`
}
En el ejemplo anterior, Name
y Count
se definen como de puntero a tipo para eliminar la ambigüedad entre un valor que falta (nil
) y un valor cero (0), que podrían tener diferencias semánticas.
En una operación HTTP PATCH, los campos con el valor nil
no afectan al valor en el recurso del servidor. Al actualizar el campo Count
de un widget, especifique el nuevo valor de Count
, dejando Name
como nil
.
Para satisfacer el requisito de enviar un elemento JSON null
, se usa la función NullValue
:
w := Widget{
Count: azcore.NullValue(0).(*int),
}
Este código establece Count
en un elemento JSON null
explícito. Cuando se envía la solicitud al servidor, se elimina el campo del Count
recurso.