Partilhar via


Padrões de uso comuns no SDK do Azure para Go

O pacote Azure Core (azcore) no SDK do Azure para Go implementa vários padrões que são aplicados em todo o SDK:

Paginação (métodos que retornam coleções)

Muitos serviços do Azure retornam coleções de itens. Como o número de itens pode ser grande, esses métodos de cliente retornam um Pager, que permite que seu aplicativo processe uma página de resultados de cada vez. Esses tipos são definidos individualmente para vários contextos, mas compartilham características comuns, como um NextPage método.

Por exemplo, suponha que há um ListWidgets método que retorna um WidgetPagerarquivo . Em seguida, você usaria o WidgetPager como mostrado aqui:

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

Operações de longa duração

Algumas operações no Azure podem levar muito tempo para serem concluídas, de alguns segundos a alguns dias. Exemplos de tais operações incluem copiar dados de uma URL de origem para um blob de armazenamento ou treinar um modelo de IA para reconhecer formulários. Essas operações de longa duração (LROs) não se adequam ao fluxo HTTP padrão de uma solicitação e resposta relativamente rápidas.

Por convenção, os métodos que iniciam um LRO são prefixados com "Begin" e retornam um Poller. O Poller é usado para sondar periodicamente o serviço até que a operação termine.

Os exemplos a seguir ilustram vários padrões para lidar com LROs. Você também pode aprender mais com o código-fonte poller.go no SDK.

Bloquear chamada para PollUntilDone

PollUntilDone lida com toda a extensão de uma operação de sondagem até que um estado terminal seja atingido. Em seguida, ele retorna a resposta HTTP final para a operação de sondagem com o conteúdo da carga útil na respType interface.

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)

Loop de sondagem personalizado

Poll envia uma solicitação de sondagem para o ponto de extremidade de sondagem e retorna a resposta ou um erro.

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)

Retomar de uma operação anterior

Extraia e salve o token de currículo de um Poller existente.

Para retomar a sondagem, talvez em outro processo ou em outro computador, crie uma nova PollerResponse instância e, em seguida, inicialize-a chamando seu Resume método, passando-lhe o token de currículo salvo anteriormente.

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)

Fluxo de pipeline HTTP

Os vários clientes SDK fornecem uma abstração sobre a API REST do Azure para habilitar o auto-completar de código e a segurança de tipo em tempo de compilação para que você não precise lidar com mecânicas de transporte de nível inferior por HTTP. No entanto, você pode personalizar a mecânica de transporte (como novas tentativas e registro).

O SDK faz solicitações HTTP por meio de um pipeline HTTP. O pipeline descreve a sequência de etapas executadas para cada viagem de ida e volta de solicitação-resposta HTTP.

O gasoduto é composto por um transporte juntamente com qualquer número de políticas:

  • O transporte envia o pedido ao serviço e recebe a resposta.
  • Cada política conclui uma ação específica no pipeline.

O diagrama a seguir ilustra o fluxo de um pipeline:

Diagrama que mostra o fluxo de um pipeline.

Todos os pacotes de cliente compartilham um pacote principal chamado azcore. Este pacote constrói o pipeline HTTP com seu conjunto ordenado de políticas, garantindo que todos os pacotes de cliente se comportem de forma consistente.

Quando uma solicitação HTTP é enviada, todas as políticas são executadas na ordem em que foram adicionadas ao pipeline antes que a solicitação seja enviada ao ponto de extremidade HTTP. Essas políticas normalmente adicionam cabeçalhos de solicitação ou registram a solicitação HTTP de saída.

Depois que o serviço do Azure responde, todas as políticas são executadas na ordem inversa antes que a resposta retorne ao seu código. A maioria das políticas ignora a resposta, mas a política de registro registra a resposta. A política de repetição pode reemitir a solicitação, tornando seu aplicativo mais resiliente a falhas de rede.

Cada política é fornecida com os dados de solicitação ou resposta necessários, juntamente com qualquer contexto necessário para executar a política. A política conclui sua operação com os dados fornecidos e, em seguida, passa o controle para a próxima política no pipeline.

Por padrão, cada pacote de cliente cria um pipeline configurado para funcionar com esse serviço específico do Azure. Você também pode definir suas próprias políticas personalizadas e inseri-las no pipeline HTTP ao criar um cliente.

Principais políticas de pipeline HTTP

O pacote Core fornece três políticas HTTP que fazem parte de cada pipeline:

Políticas de pipeline HTTP personalizadas

Você pode definir sua própria política personalizada para adicionar recursos além do conteúdo do pacote principal. Por exemplo, para ver como seu aplicativo lida com falhas de rede ou serviço, você pode criar uma política que injete falhas quando solicitações são feitas durante o teste. Ou você pode criar uma política que zomba do comportamento de um serviço para teste.

Para criar uma política HTTP personalizada, defina sua própria estrutura com um Do método que implementa a Policy interface:

  1. O método da Do sua política deve executar operações conforme necessário no arquivo policy.Request. Exemplos de operações incluem registro, injeção de uma falha ou modificação de qualquer URL da solicitação, parâmetros de consulta ou cabeçalhos de solicitação.
  2. O Do método encaminha a solicitação (modificada) para a próxima política no pipeline chamando o método da Next solicitação.
  3. Next retorna o http.Response e um erro. Sua política pode executar qualquer operação necessária, como registrar a resposta/erro.
  4. Sua política deve retornar uma resposta e um erro à política anterior no pipeline.

Nota

As políticas devem ser seguras para a rotina. A segurança Goroutine permite que vários goroutines acessem um único objeto cliente simultaneamente. É comum que uma política seja imutável depois de criada. Esta imutabilidade garante que a rotina é segura.

Modelo de política personalizado

O código a seguir pode ser usado como ponto de partida para definir uma política 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

Um transporte envia uma solicitação HTTP e retorna sua resposta/erro. A primeira política a lidar com a solicitação também é a última política que lida com a resposta antes de retornar a resposta/erro de volta às políticas do pipeline (na ordem inversa). A última política em preparação invoca o transporte.

Por padrão, os clientes usam o compartilhado http.Client da biblioteca padrão do Go.

Você cria um transporte com ou sem monitoração de estado personalizado da mesma forma que cria uma política personalizada. No caso stateful, você implementa o Do método herdado da interface do Transporter . Em ambos os casos, sua função ou Do método recebe novamente um azcore.Request, retorna um azCore.Responsee executa ações na mesma ordem de uma política.

Como excluir um campo JSON quando você invoca uma operação do Azure

Operações como JSON-MERGE-PATCH enviar um JSON null para indicar que um campo deve ser excluído (junto com seu valor):

{
    "delete-me": null
}

Esse comportamento entra em conflito com o marshaling padrão do SDK que especifica omitempty como uma maneira de resolver a ambiguidade entre um campo a ser excluído e seu valor zero.

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

No exemplo anterior, Name e Count são definidos como ponteiro para tipo para desambiguar entre um valor ausente (nil) e um valor zero (0), que pode ter diferenças semânticas.

Em uma operação HTTP PATCH, quaisquer campos com o valor nil não afetam o valor no recurso do servidor. Ao atualizar o campo de Count um Widget, especifique o novo valor para Count, deixando Name como nil.

Para cumprir o requisito de envio de um JSON null, a NullValue função é usada:

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

Este código é definido Count como um JSON nullexplícito. Quando a solicitação é enviada ao servidor, o campo do Count recurso é excluído.

Consulte também