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

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

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

多くの Azure サービスでは、項目のコレクションが返されます。 項目の数が多くなる可能性があるため、これらのクライアント メソッドでは "ページャー" が返されます。これにより、一度に 1 ページ分の結果をアプリケーション側で処理できるようになります。 これらの型はさまざまなコンテキストで個別に定義されますが、NextPage メソッドのような共通の特性を備えています。

たとえば、WidgetPager を返す ListWidgets メソッドがあるとします。 その後、ここに示されているように 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" というプレフィックスが付き、"ポーラー" が返されます。 ポーラーは、操作が完了するまでサービスを定期的にポーリングするために使用されます。

次の例は、LRO を処理するさまざまなパターンを示しています。 SDK の poller.go ソース コードで、もっと詳しい内容を確認することもできます。

PollUntilDone の呼び出しをブロックする

PollUntilDone は、ポーリング操作の範囲全体を、終了状態に達するまで処理します。 次に、インターフェイス内のペイロードの内容を含むポーリング操作の最終的な HTTP 応答を 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)

カスタマイズされたポーリング ループ

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)

前の操作から再開する

既存のポーラーから再開トークンを抽出して保存します。

ポーリングを (別のプロセスまたは PC などで) 再開するには、新しい 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 要求と応答のラウンドトリップごとに実行される一連の手順が記述されています。

パイプラインは 1 つのトランスポートおよび任意の数のポリシーで構成されます。

  • "トランスポート" は要求をサービスに送信し、応答を受信します。
  • 各 "ポリシー" はパイプラインで特定のアクションを実行します。

次の図に、パイプラインのフローを示します。

パイプラインのフローを示す図。

すべてのクライアント パッケージで、"コア" パッケージ (azcore という名前) が共有されます。 このパッケージで構築される HTTP パイプラインとその順序付けされた一連のポリシーにより、すべてのクライアント パッケージが一貫して動作するようになります。

HTTP 要求を送信すると、パイプラインに追加された順序ですべてのポリシーが実行されてから、要求が HTTP エンドポイントに送信されます。 これらのポリシーでは、通常、要求ヘッダーが追加されるか、送信 HTTP 要求がログに記録されます。

Azure サービスが応答した後、応答がコードに戻る前に、すべてのポリシーが逆の順序で実行されます。 ほとんどのポリシーで応答が無視されますが、ログ ポリシーによって応答が記録されます。 再試行ポリシーによって要求が再発行され、ネットワーク障害に対するアプリの回復性が向上する可能性があります。

各ポリシーには、必要な要求または応答のデータが、ポリシーを実行するために必要なコンテキストと共に提供されます。 提供されたデータを使用してポリシーの操作が完了すると、パイプライン内の次のポリシーに制御が渡されます。

既定では、各クライアント パッケージによって、その特定の Azure サービスを使用するように構成されたパイプラインが作成されます。 クライアントを作成するときに、独自のカスタム ポリシーを定義して HTTP パイプラインに挿入することもできます。

コアの HTTP パイプライン ポリシー

コア パッケージには、次の 3 つの HTTP ポリシーが用意されており、これはすべてのパイプラインに含まれます。

カスタムの HTTP パイプライン ポリシー

独自のカスタム ポリシーを定義して、Core パッケージの内容を超える機能を追加できます。 たとえば、アプリがネットワークまたはサービスの障害にどのように対処するのかを確認するためには、テスト中に要求を行うとエラーを挿入するポリシーを作成できます。 または、テストのためにサービスの動作をモックするポリシーを作成できます。

カスタムの HTTP ポリシーを作成するには、Policy インターフェイスを実装する Do メソッドを使用して独自の構造体を定義します。

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

Note

ポリシーは goroutine セーフである必要があります。 goroutine セーフであることによって、複数の goroutine が 1 つのクライアント オブジェクトに同時にアクセスできます。 ポリシーは、作成後に変更できないのが一般的です。 この不変性により、goroutine が安全であることが保証されます。

カスタム ポリシー テンプレート

次のコードは、カスタム ポリシーを定義するための開始点として使用できます。

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 を使用します。

カスタムのステートフルまたはステートレスのトランスポートは、カスタム ポリシーと同じ方法で作成します。 ステートフルのケースでは、Transporter インターフェイスから継承された Do メソッドを実装します。 どちらの場合も、関数または Do メソッドによって再び azcore.Request が受信され、azCore.Response が返され、ポリシーと同じ順序でアクションが実行されます。

Azure 操作を呼び出す際に JSON フィールドを削除する方法

JSON-MERGE-PATCH のような操作では、フィールドを (その値と共に) 削除する必要があることを示すために、JSON に null が送信されます。

{
    "delete-me": null
}

この動作は、除外するフィールドとその 0 値の間のあいまい性を解決する手段として omitempty を指定する、SDK の既定のマーシャリングと矛盾します。

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

前の例では、セマンティックの違いを持つ可能性がある欠落値 (nil) とゼロ値 (0) の間のあいまいさを解消するために、Name および Count が型のポインターとして定義されています。

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

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

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

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

関連項目