次の方法で共有


Azure SDK for Go の一般的な使用パターン

Azure SDK for Go の Azure Core (azcore) パッケージには、SDK 全体に適用されるいくつかのパターンが実装されています。

コレクションを返すメソッド(ページネーション)

多くの 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 メソッドを使用して独自の構造を定義します。

  1. ポリシーの Do メソッドは、受信 policy.Requestで必要に応じて操作を実行する必要があります。 操作の例としては、ログ記録、エラーの挿入、要求の URL、クエリ パラメーター、要求ヘッダーの変更などがあります。
  2. Do メソッドは、要求のNext メソッドを呼び出すことによって、パイプライン内の次のポリシーに要求を転送します。
  3. Next は、 http.Response とエラーを返します。 ポリシーは、応答/エラーのログ記録など、必要な操作を実行できます。
  4. ポリシーは、パイプライン内の前のポリシーに応答とエラーを返す必要があります。

ポリシーは、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"`
}

前の例では、 NameCount は、欠損値 (nil) と 0 値 (0) の間であいまいさを解消するためのポインター型として定義されています。セマンティックな違いがある可能性があります。

HTTP PATCH 操作では、 nil 値を持つフィールドは、サーバーのリソース内の値には影響しません。 ウィジェットの Count フィールドを更新する場合は、 Countの新しい値を指定し、 Namenilのままにします。

JSON nullを送信するための要件を満たすために、 NullValue 関数が使用されます。

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

このコードでは、 Count を明示的な JSON nullに設定します。 要求がサーバーに送信されると、リソースの Count フィールドが削除されます。

こちらも参照ください