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:
- Fluxo de pipeline HTTP, que é o mecanismo HTTP subjacente usado por bibliotecas de clientes do SDK.
- Paginação (métodos que retornam coleções).
- LROs (Operações de Execução Prolongada).
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:
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
:
- O método da política
Do
deve executar operações conforme necessário nopolicy.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. - O método
Do
encaminha a solicitação (alterada) para a próxima política no pipeline executando uma chamada ao métodoNext
da solicitação. Next
retornahttp.Response
e um erro. A política pode executar qualquer operação necessária, como registrar em log a resposta ou o erro.- 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.