Compartilhar via


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

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

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

Vários 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 o aplicativo processe uma página de resultados de cada vez. Esses tipos são definidos de modo individual para vários contextos. Contudo, eles compartilham características comuns, como um método NextPage.

Por exemplo, vamos supor que há um método ListWidgets que retorna um WidgetPager. Você usaria o WidgetPager 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

Pode levar muito tempo para concluir algumas operações no Azure, de alguns segundos a poucos dias. Exemplos dessas 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 execução (LROs) são inadequadas para o fluxo HTTP padrão de uma solicitação e resposta relativamente rápidas.

Por convenção, os métodos que iniciam uma LRO são prefixados com "Begin" e retornam um instrumento de sondagem. O instrumento de sondagem é usado para executar de modo periódico uma sondagem no serviço até que a operação seja concluída.

Os exemplos a seguir vão ilustrar vários padrões para lidar com LROs. Também é possível saber mais sobre o código-fonte poller.go no SDK.

Como bloquear uma chamada ao PollUntilDone

O PollUntilDone lida com todo o intervalo da operação de sondagem até alcançar um estado terminal. 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 uma 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 retomada de um instrumento de sondagem atual.

Para retomar a sondagem, talvez em outro processo ou computador, crie uma instância PollerResponse, depois inicialize essa instância executando uma chamada ao método Resume, passando o token de retomada 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 uma API REST do Azure para habilitar a autocompletar 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 executa solicitações HTTP por meio de um pipeline HTTP. O pipeline descreve a sequência de etapas executadas em cada viagem de ida e volta da solicitação-resposta HTTP.

O pipeline é composto de um transporte, juntamente com várias políticas:

  • O transporte envia uma solicitação para o serviço e recebe uma resposta.
  • Cada política executa uma ação específica no pipeline.

O seguinte diagrama ilustrará o fluxo de um pipeline:

Diagrama que mostra o fluxo de um pipeline.

Todos os pacotes de cliente compartilham um pacote Core chamado azcore. Esse pacote cria o pipeline HTTP com o respectivo conjunto ordenado de políticas, garantindo que todos os pacotes de cliente se comportem de modo consistente.

Quando uma solicitação HTTP é enviada, todas as políticas são executadas na ordem em que elas foram adicionadas ao pipeline antes que a solicitação seja enviada para o ponto de extremidade HTTP. Essas políticas geralmente 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 código. A maioria das políticas ignora a resposta, mas a política de registro em log a registra. A política de repetição pode reemitir a solicitação, tornando seu aplicativo mais resistente a falhas de rede.

Cada política é fornecida com os dados necessários de solicitação ou resposta, juntamente com o contexto necessário para executá-la. A política executa a respectiva operação com os dados fornecidos e 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. Também é possível definir políticas personalizadas próprias e inseri-las no pipeline HTTP durante a criação de um cliente.

Principais políticas do 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 Core. Por exemplo, para ver como o aplicativo lida com falhas de rede ou de serviço, você pode criar uma política que injete uma falha quando são feitas solicitações durante os testes. Também é possível criar uma política que simula o comportamento de um serviço para teste.

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

  1. O método da política Do deve executar operações conforme necessário no policy.Request de entrada. Alguns exemplos de operações: registro em log, injeção de uma falha ou alteração de uma das URLs da solicitação, dos parâmetros de consulta ou dos cabeçalhos de solicitação.
  2. O método Do encaminha a solicitação (alterada) para a próxima política no pipeline executando uma chamada ao método Next da solicitação.
  3. Next retorna http.Response e um erro. A política pode executar qualquer operação necessária, como registrar em log a resposta ou o erro.
  4. A sua política deverá retornar uma resposta e um erro para a política anterior no pipeline.

Observação

As políticas devem ser seguras para goroutine. Com isso, várias goroutines podem acessar simultaneamente o mesmo objeto cliente. É comum que uma política seja imutável depois de criada. Essa imutabilidade garante que a goroutine seja 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 uma resposta/um erro. A primeira política a manipular a solicitação também é a última política que manipula a resposta antes de retornar a resposta/erro de volta às políticas do pipeline (em ordem inversa). A última política no pipeline invoca o transporte.

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

Crie um transporte personalizado com ou sem estado do mesmo modo que uma política personalizada. No caso com estado, implemente o método Do herdado da interface Transporter. Nos dois casos, a função ou o método Do recebe novamente um azcore.Request, retorna um azCore.Response, depois executa ações na mesma ordem que uma política.

Como excluir um campo JSON ao invocar uma operação do Azure

As operações como JSON-MERGE-PATCH enviam um JSON null para indicar que um campo deverá ser excluído (juntamente com o respectivo valor):

{
    "delete-me": null
}

Esse comportamento está em desacordo com o marshaling padrão do SDK, que especifica omitempty como um meio de resolver a ambiguidade entre um campo a ser excluído e o respectivo valor zero.

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

No exemplo anterior, Name e Count estão definidos como um ponteiro para tipo a fim de eliminar uma ambiguidade entre um valor ausente (nil) e um valor zero (0) que poderá 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 Count de um Widget, especifique o novo valor para Count, mantendo Name como nil.

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

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

Esse código define Count como um JSON null explícito. Quando a solicitação é enviada ao servidor, o campo do Count recurso é excluído.

Confira também