Remote API で App Engine にアクセスする

Google Cloud CLI の remote_api パッケージを使用すると、どの Go アプリケーションからでも App Engine サービスに透過的にアクセスできます。たとえば、Remote API を使用して、ローカルマシンで実行されているアプリや、別の App Engine アプリの 1 つから Datastore にアクセスできます。

remote_api パッケージの内容については、remote_api パッケージ リファレンスをご覧ください。

Remote API の有効化

まず、remote_api url ハンドラを app.yaml に追加します。次に例を示します。

- url: /_ah/remote_api
  script: _go_app

これにより、URL /_ah/remote_api が Go アプリにマッピングされます。この URL へのアクセスは、Remote API ハンドラによって、アプリケーションの管理者に制限されます。

次に、任意の .go ソースファイルに以下の行を追加して、プロジェクトのいずれかのパッケージで remote_api パッケージをインポートします。

import _ "google.golang.org/appengine/remote_api"

プログラムの初期化中、remote_api パッケージは /_ah/remote_api パスのハンドラを登録します。import 宣言のアンダースコアは、「このパッケージをインポートするが、すぐに使用しない」ことを意味します。アンダースコアを付けないと、コンパイル時に「インポートされたが使用されていない」ことを示すエラー メッセージが表示されます。

最後に、App Engine にアップデートをデプロイします。次に例を示します。

gcloud app deploy app.yaml

ローカル クライアントでの Remote API の使用

Remote API を使用すると、App Engine サービスを使用してデータストアにアクセスするローカル アプリケーションを作成できます。Remote API を使用すると、アクセス対象のアプリケーションで割り当てを使用することになりますので、ご注意ください。

開始する前に、App Engine アプリケーションで Remote API が有効になっていることを確認してください。ローカル アプリケーションで Remote API を使用するには、remote_api.NewRemoteContext を使用してコンテキストを作成し、そのコンテキストをすべての API 呼び出しで通常の App Engine コンテキストの代わりに使用します。

// Copyright 2011 Google Inc. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.

package main

import (
	"log"
	"time"

	"golang.org/x/net/context"
	"golang.org/x/oauth2/google"

	"google.golang.org/appengine/datastore"
	"google.golang.org/appengine/remote_api"
)

func main() {
	const host = "<your-app-id>.appspot.com"

	ctx := context.Background()

	hc, err := google.DefaultClient(ctx,
		"https://www.googleapis.com/auth/appengine.apis",
		"https://www.googleapis.com/auth/userinfo.email",
		"https://www.googleapis.com/auth/cloud-platform",
	)
	if err != nil {
		log.Fatal(err)
	}

	remoteCtx, err := remote_api.NewRemoteContext(host, hc)
	if err != nil {
		log.Fatal(err)
	}

	q := datastore.NewQuery("Greeting").
		Filter("Date >=", time.Now().AddDate(0, 0, -7))

	log.Println(q.Count(remoteCtx))
}

このコードを実行するには、次のパッケージを取得する必要があります。

$ go get google.golang.org/appengine/...
$ go get golang.org/x/oauth2/...

サーバーのホスト名と http.Client は、NewRemoteContext の呼び出しで指定する必要があります。指定された http.Client は、各リクエストで必要な認証情報を渡す役割を果たします。

上記のサンプルでは、golang.org/x/oauth2/google パッケージの DefaultClient が、アプリケーションのデフォルト認証情報を使用して OAuth 2 の認証情報を指定しています。

制限事項とベスト プラクティス

remote_api モジュールでは、できる限りネイティブの App Engine データストアと同じ動作をするように最大限の努力が払われています。このため、効率が犠牲になる場合があります。remote_api を使用する際には、以下の点に留意してください。

どのデータストア リクエストにもラウンドトリップがある

HTTP を使ってデータストアにアクセスするため、ローカルなアクセスより若干オーバーヘッドとレイテンシが増えます。高速化と負荷軽減のため、get と put をバッチ処理したり、クエリからエンティティをバッチで取得したりすることによって、ラウンドトリップの回数を減らすようにしてください。これは、remote_api に限らず、データストアの通常の使用方法でも役に立ちます。バッチ オペレーションは単一の Datastore オペレーションと見なされるためです。

remote_api へのリクエストは割り当てを消費する

remote_api では HTTP を使用するため、データストアの API を呼び出すたびに、データストアに対する通常の割り当てに加えて、HTTP リクエストと入出力バイト数の割り当て使用量を消費します。remote_api を使って一括更新を行っている場合には、この点に注意してください。

API での 1 MB の制限が適用される

API のリクエストとレスポンスに対する 1 MB の制限は、ネイティブに実行している場合と同じように適用されます。エンティティが特に大きい場合は、この制限に達しないように、一度に取得または put する数を制限する必要があります。残念ながら、これはラウンドトリップを減らすことと矛盾します。このため、リクエストまたはレスポンスのサイズ制限を超えない範囲で、できるだけ大きなバッチを使用することをおすすめします。ただし、これが問題となる可能性のあるエンティティは多くありません。

クエリの反復処理を避ける

クエリに反復処理をすると、SDK はデータストアから 20 個のバッチでエンティティを取得し、取得済みのエンティティを使い果たすたびに新しいバッチを取得します。各バッチは remote_api による個別のリクエストで取得する必要があるため、効率的に実行することができません。その代わりに、remote_api は各バッチに対してまったく新しいクエリを実行し、オフセット機能を使用して結果の途中から取得します。

必要なエンティティ数がわかる場合は、その個数を指定することにより、1 回のリクエストで全体を取得できます。

必要なエンティティ数がわからない場合は、カーソルを使用して大きな結果セットに対する反復処理を効率的に実行できます。また、これによって通常のデータストア クエリに設定されている 1,000 エンティティの制限を回避することもできます。

トランザクションは効率が悪い

remote_api を使ったトランザクションの実装では、トランザクション中に取得されたエンティティに関する情報と、トランザクション中に put または delete されたエンティティのコピーを蓄積します。トランザクションが commit されると、この情報のすべてが App Engine サーバーに渡されます。そこでは、トランザクションで使用されたすべてのエンティティが再度取得され、変更がないことを確認します。そして、トランザクションが行ったすべての変更を put と delete で実行し、commit します。競合がある場合、サーバーはトランザクションをロールバックしてクライアント側に通知します。クライアント側はこの手順をもう一度最初から繰り返す必要があります。

このアプローチは正しく動作し、機能的にはローカルのデータストアでのトランザクションとまったく同じですが、かなり非効率です。必要なところには必ずトランザクションを使用しつつ、効率の観点から、実行するトランザクションの数と複雑さを減らすようにしてください。