Azure SDK for Go の Azure Core (azcore
) パッケージには、SDK 全体に適用されるいくつかのパターンが実装されています。
- HTTP パイプライン フロー。これは、SDK のクライアント ライブラリによって使用される基になる HTTP メカニズムです。
- ページネーション(コレクションを返すメソッド)。
- 実行時間の長い操作 (LRO)。
コレクションを返すメソッド(ページネーション)
多くの Azure サービスでは、項目のコレクションが返されます。 項目の数が多い場合があるため、これらのクライアント メソッドは Pager を返します。これにより、アプリは一度に 1 ページの結果を処理できます。 これらの型は、さまざまなコンテキストに対して個別に定義されますが、 NextPage
メソッドのような共通の特性を共有します。
たとえば、ListWidgets
を返すWidgetPager
メソッドがあるとします。 次に示すように、 WidgetPager
を使用します。
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...
}
長時間実行される操作
Azure での一部の操作は、数秒から数日の間、完了するまでに長い時間がかかる場合があります。 このような操作の例としては、ソース URL からストレージ BLOB へのデータのコピーや、フォームを認識するための AI モデルのトレーニングなどがあります。 これらの 実行時間の長い操作 (LRO) は 、比較的迅速な要求と応答の標準的な HTTP フローには適していません。
慣例により、LRO を開始するメソッドにはプレフィックスとして "Begin" が付き、 Poller が返されます。 Poller は、操作が完了するまでサービスを定期的にポーリングするために使用されます。
次の例は、LRO を処理するためのさまざまなパターンを示しています。 SDK の poller.go ソース コードからさらに学習することもできます。
PollUntilDone への呼び出しをブロックする
PollUntilDone
は、ポーリング操作が終了状態に達するまで全体を管理します。 次に、 respType
インターフェイス内のペイロードの内容を含むポーリング操作の最終的な HTTP 応答を返します。
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)
カスタマイズされたポーリング ループ
Poll
はポーリング エンドポイントにポーリング要求を送信し、応答またはエラーを返します。
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)
前の操作から再開する
既存の Poller から再開トークンを抽出して保存します。
別のプロセスまたは別のコンピューターでポーリングを再開するには、新しい PollerResponse
インスタンスを作成し、 Resume
メソッドを呼び出して初期化し、以前に保存した再開トークンを渡します。
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)
HTTP パイプライン フロー
さまざまな SDK クライアントは、コード補完とコンパイル時の型の安全性を実現するために、Azure の REST API に対する抽象化を提供するため、HTTP 経由で下位レベルのトランスポートメカニズムに対処する必要はありません。 ただし、トランスポートのしくみ (再試行やログ記録など) を カスタマイズ できます。
SDK は HTTP パイプラインを介して HTTP 要求を行います。 パイプラインでは、HTTP 要求と応答のラウンド トリップごとに実行される一連の手順について説明します。
パイプラインは、トランスポートと任意の数のポリシーで構成されています。
- トランスポートは要求をサービスに送信し、応答を受信します。
- 各 ポリシー は、パイプライン内の特定のアクションを完了します。
次の図は、パイプラインのフローを示しています。
すべてのクライアント パッケージは、という名前のazcore
パッケージを共有します。 このパッケージは、順序付けされた一連のポリシーを使用して HTTP パイプラインを構築し、すべてのクライアント パッケージが一貫して動作することを保証します。
HTTP 要求が送信されると、要求が HTTP エンドポイントに送信される前に、すべてのポリシーがパイプラインに追加された順序で実行されます。 これらのポリシーは、通常、要求ヘッダーを追加するか、送信 HTTP 要求をログに記録します。
Azure サービスが応答した後、応答がコードに戻る前に、すべてのポリシーが逆の順序で実行されます。 ほとんどのポリシーは応答を無視しますが、ログ ポリシーは応答を記録します。 再試行ポリシーによって要求が再発行され、ネットワーク障害に対するアプリの回復性が向上する可能性があります。
各ポリシーには、必要な要求または応答データと、ポリシーを実行するために必要なコンテキストが用意されています。 ポリシーは、指定されたデータで操作を完了し、パイプライン内の次のポリシーに制御を渡します。
既定では、各クライアント パッケージは、その特定の Azure サービスと連携するように構成されたパイプラインを作成します。 独自の カスタム ポリシー を定義し、クライアントの作成時に HTTP パイプラインに挿入することもできます。
コア HTTP パイプライン ポリシー
コア パッケージには、すべてのパイプラインに含まれる 3 つの HTTP ポリシーが用意されています。
カスタム HTTP パイプライン ポリシー
独自のカスタム ポリシーを定義して、Core パッケージの内容を超える機能を追加できます。 たとえば、アプリがネットワークまたはサービスの障害にどのように対処するかを確認するには、テスト中に要求が行われたときにエラーを挿入するポリシーを作成できます。 または、テストのためにサービスの動作をモックするポリシーを作成することもできます。
カスタム HTTP ポリシーを作成するには、Do
インターフェイスを実装する Policy
メソッドを使用して独自の構造を定義します。
- ポリシーの
Do
メソッドは、受信policy.Request
で必要に応じて操作を実行する必要があります。 操作の例としては、ログ記録、エラーの挿入、要求の URL、クエリ パラメーター、要求ヘッダーの変更などがあります。 -
Do
メソッドは、要求のNext
メソッドを呼び出すことによって、パイプライン内の次のポリシーに要求を転送します。 -
Next
は、http.Response
とエラーを返します。 ポリシーは、応答/エラーのログ記録など、必要な操作を実行できます。 - ポリシーは、パイプライン内の前のポリシーに応答とエラーを返す必要があります。
注
ポリシーは、goroutine とともに使用する際に安全である必要があります。 Goroutine の安全性により、複数の goroutine が 1 つのクライアント オブジェクトに同時にアクセスできます。 ポリシーは、作成後に変更できないのが一般的です。 この不変性は、ゴルーチンが安全であることを保証します。
カスタム ポリシー テンプレート
次のコードは、カスタム ポリシーを定義するための開始点として使用できます。
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
}
カスタム HTTP トランスポート
トランスポートは HTTP 要求を送信し、その応答/エラーを返します。 要求を処理する最初のポリシーは、応答/エラーをパイプラインのポリシー (逆順) に返す前に応答を処理する最後のポリシーでもあります。 パイプラインの最後のポリシーがトランスポートを呼び出します。
既定では、クライアントは Go の標準ライブラリの共有 http.Client
を使用します。
カスタム ポリシーを作成するのと同じ方法で、カスタム ステートフルトランスポートまたはステートレス トランスポートを作成します。 ステートフルなケースでは、Do
から継承された メソッドを実装します。 どちらの場合も、関数または Do
メソッドは再び azcore.Request
を受け取り、 azCore.Response
を返し、ポリシーと同じ順序でアクションを実行します。
Azure 操作を呼び出すときに JSON フィールドを削除する方法
JSON JSON-MERGE-PATCH
を送信null
などの操作は、フィールドを削除する必要があることを示します (その値と共に)。
{
"delete-me": null
}
この動作は、除外するフィールドとそのゼロ値の間のあいまいさを解決する方法として omitempty
を指定する SDK の既定のマーシャリングと競合します。
type Widget struct {
Name *string `json:",omitempty"`
Count *int `json:",omitempty"`
}
前の例では、 Name
と Count
は、欠損値 (nil
) と 0 値 (0) の間であいまいさを解消するためのポインター型として定義されています。セマンティックな違いがある可能性があります。
HTTP PATCH 操作では、 nil
値を持つフィールドは、サーバーのリソース内の値には影響しません。 ウィジェットの Count
フィールドを更新する場合は、 Count
の新しい値を指定し、 Name
は nil
のままにします。
JSON null
を送信するための要件を満たすために、 NullValue
関数が使用されます。
w := Widget{
Count: azcore.NullValue(0).(*int),
}
このコードでは、 Count
を明示的な JSON null
に設定します。 要求がサーバーに送信されると、リソースの Count
フィールドが削除されます。