Compartilhar via


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

O pacote do Azure Core (azcore) no SDK do Azure para Go implementa vários padrões 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 cliente retornam um Pager, o 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 haja um ListWidgets método que retorna um WidgetPager. Em seguida, você usaria o WidgetPager conforme 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, em qualquer lugar, de alguns segundos a alguns 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 LROs (operações de execução longa) 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 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 seja concluída.

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

Bloquear chamada para PollUntilDone

PollUntilDone gerencia todo o intervalo de uma operação de sondagem até que um estado terminal seja atingido. Em seguida, retorna a resposta HTTP final para a operação de polling com o conteúdo do payload na interface respType.

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 pesquisa personalizado

Poll envia uma solicitação de sondagem para o endpoint 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 uma operação anterior

Extraia e salve o token de retomada 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-o 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 do SDK fornecem uma abstração sobre a API REST do Azure para habilitar a conclusão do código e a segurança do tipo de tempo de compilação para que você não precise lidar com a mecânica de transporte de nível inferior por HTTP. No entanto, você pode personalizar a mecânica de transporte (como repetições e registro em log).

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

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

  • O transporte envia a solicitação para o serviço e recebe a resposta.
  • Cada política conclui uma ação específica no fluxo de trabalho.

O diagrama a seguir ilustra o fluxo de um pipeline:

Diagrama que mostra o fluxo de uma tubulação.

Todos os pacotes de cliente compartilham um pacote Core chamado azcore. Esse pacote constrói o pipeline HTTP com seu conjunto ordenado de políticas, garantindo que todos os pacotes 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 responder, todas as políticas são executadas na ordem inversa antes que a resposta seja enviada 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 reemissar 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 especificados e passa o controle para a próxima política no fluxo.

Por padrão, cada pacote 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 fluxo 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 seu aplicativo lida com falhas de rede ou serviço, você pode criar uma política que injeta falha quando solicitações são feitas durante o teste. Ou você pode criar uma política que simula o 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 política deve executar operações conforme necessário na entrada policy.Request. Exemplos de operações incluem registro em log, 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 método Do encaminha a solicitação (modificada) para a próxima política no pipeline chamando o método Next da solicitação.
  3. Next retorna o http.Response e um erro. Sua política pode executar qualquer operação necessária, como registrar em log a resposta/erro.
  4. Sua política deve retornar uma resposta e um erro de volta à política anterior no pipeline.

Observação

As políticas devem ser goroutine-safe. A segurança de goroutines permite que várias goroutines acessem simultaneamente um único objeto de cliente. É comum que uma política seja imutável após a criação. Essa imutabilidade garante que a goroutine esteja 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 para lidar com 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 fluxo de trabalho invoca o transporte.

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

Você cria um transporte personalizado com estado ou sem estado da mesma forma que cria uma política personalizada. No caso com estado, você implementa o método Do herdado da interface 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 que uma política.

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

Operações como JSON-MERGE-PATCH enviar um JSON null para indicar que um campo deve ser excluído (juntamente 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 PATCH HTTP, todos os 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 atender ao requisito de envio de um JSON null, a NullValue função é 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.

Consulte também