Multitenancy
Multitenancyとは1つのアプリケーションインスタンスが複数のクライアント(ユーザーグループ)に対してサービスを行うソフトウェアアーキテクチャ。
ドキュメントによは、次のような用途が記載されている。
- ユーザーインフォメーションを分類する
- アドミニストレーターデータをアプリケーションデータから分離する
- テストと完成品のために、分離されたデータストアインスタンスを作り出す
- 多数のアプリケーションを一つのAppEngineのインスタンス上で走らせる
つまりうっかり混ざってほしくないデータを分けて保存したい場合に使えるようだ。Namespace APIを使ってネームスペース設定することでデータをネームスペースごとに分けて保存することができる。
ネームスペースに対応したAppEngine API
ネームスペースが使えるのは
- Datastore
- Memcache
- Task Queue
だけ。
Blobstoreではネームスペースは使えない。
Namespace APIを使う
Multitenancyは Namespaces APIを使って実現する。使い方は簡単で、NamespaceManager.set(String) メソッドを呼ぶだけでカレントネームスペースを設定できる。
ネームスペースに設定できる文字列は100文字までの英数字、'-'、'_'、'.'で構成された文字列。アンダーバーで始まる文字列はシステム予約なので使えない。
ネームスペースを設定しなければカレントネームスペースはnullで、この場合各種AppEngine APIは空文字列("")のネームスペースを使用する。
ネームスペースはリクエストごとに設定する必要がある。各リクエストが開始された時点ではカレントネームスペースは設定されていない。
データ漏洩を避ける
ネームスペースで分離したデータがネームスペースを超えて漏洩しないように注意しなければならない。ネームスペースは文字列ひとつで設定できるのでうっかりすればネームスペースを超えたデータにアクセスできてしまう。
Blobsoreはネームスペースをサポートしないので、ネームスペースを使うアプリケーションでは、blobキーをネームスペースつきのDatastoreに保存してこのDatastoreデータを経由してblobにアクセスする。ブラウザから渡されたblobキーで直接blobにアクセスしてしまうと分離したいデータにアクセスできてしまうかもしれない。
Datastore
Datastore APIを呼び出す前にNamespaceManagerでカレントネームスペースを設定しておくだけで良い。
KeyやQueryを作るとき、APIはカレントネームスペースを参照してKey、Queryオブジェクトにネームスペースを設定する。先祖を指定してキーを作成すると、新しいキーは先祖キーのネームスペースを継承する。
なお、KeyやQueryに明示的にネームスペースを設定するJava APIは無い。
Key、Queryオブジェクトをシリアライズしたデータにはネームスペースも含まれる。これらをデシリアライズして使うときにはネームスペースが適切かどうか注意する必要がある。
カレントネームスペースが”a”のときに作られたキーは、シリアライズされてカレントネームスペース”b”でデシリアライズされてもキーのネームスペースは”a”のままで復元される。これをDatastoreにストアするとネームスペース”a”にストアされる。
信用できないソース(ブラウザなど)から渡されたKeyでDatastoreにアクセスするとネームスペースを超えてデータにアクセスしてしまう危険があるので、受け取ったキーのネームスペースが適切かどうか検証してから使用すること。
Memcache
MemcacheService を作成するときに明示的にネームスペースを設定しなければ、MemcacheService はメソッドが呼ばれるときにカレントネームスペースの設定を参照して memcache にアクセスする。
下のコード例はドキュメントから引用。
// Create a MemcacheService that uses the current namespace by
// calling NamespaceManager.get() for every access.
MemcacheService current =
MemcacheServiceFactory.getMemcacheService();
// stores value in namespace "abc"
String oldNamespace = NamespaceManager.get();
NamespaceManager.set("abc");
try {
current.put("key", value); // stores value in namespace “abc”
} finally {
NamespaceManager.set(oldNamespace);
}
MemcacheServiceFactory.getMemcacheService(String) で明示的にネームスペースを指定すると、カレントネームスペースを無視して指定したネームスペースで memcache にアクセスする。
MemcacheService boundMemcache =
MemcacheServiceFactory.getMemcacheService("specific-namespace");
NamespaceManager.set("whatever-namespace");
// このレコードはネームスペース"specific-namespace"でストアされる。
boundMemcache.put("key3", "value3");
Task Queue
タスクが作られるときにNamespaceManagerにセットしたカレントネームスペースとGoogle Appsドメインも(もしあれば)共にキューにプッシュされる。
タスクが実行されるとき、カレントネームスペースとGoogle Appsドメインが復元される。もしタスクをaddするときにカレントネームスペースが設定されていなければ、タスクが実行されるとき空のネームスペースが設定される。
タスク名はネームスペースで分離されないのですべてのネームスペースに渡ってユニークな名前にしないとぶつかる。
Queueからタスクをプルする場合はネームスペースの機能は提供されない。自前でタスクのネームスペースを復元できるようにペイロードにネームスペースを入れるなどの策が必要。
API
Class NamespaceManager
カレントネームスペースを操作する機能を提供する。
カレントネームスペースはget()によって返される文字列。Datastore、Memcache、Task QueueのAPIで使われる。
ネームスペースに関連したクラス(例えば、Key、Query、MemcacheService)が作られるとき、ネームスペースが決まっていなければget()の呼び出しで決定される。もしget()がnullを返したらカレントネームスペースはセットされておらずこれらのAPIは空の("")ネームスペースを使う。
例:
NamespaceManager.set("a-namespace");
MemcacheService memcache =
MemcacheServiceFactory.getMemcacheService();
// Store record in namespace "a-namespace"
memcache.put("key1", "value1");
NamespaceManager.set("other-namespace");
// Store record in namespace "other-namespace"
memcache.put("key2", "value2");
MemcacheService boundMemcache =
MemcacheServiceFactory.getMemcacheService("specific-namespace");
NamespaceManager.set("whatever-namespace");
// このレコードはネームスペース"specific-namespace"でストアされる。
boundMemcache.put("key3", "value3");
MemcacheService memcache (上記の例で)はカレントネームスペースを使う。そして key1 がネームスペース「a-namespace」にストアされる。key2 はネームスペース「other-namespace」にストアされる。カレントネームスペースに優先してデータを特定のネームスペースにストアもできる。上記の例で key3 はネームスペース「specific-namespace」にストアされる。
Task Queueの Queue.add() メソッドは追加されるタスクの中にNamaspaceManagerの設定を転送し、追加されたタスクはタスクを作ったものと同じカレントネームスペースで実行される。
例外的に、カレントネームスペースが設定されていないとき(つまりget()がnullを返すとき)は空のネームスペース("")が作られたタスクのリクエストに転送される。
メソッド
public static void set(java.lang.String newNamespace)
ネームスペースに関連したサービスのネームスペース初期化に使われる値をセットする。
Parameters:
newNamespace - 新しいネームスペース。
Throws:
java.lang.IllegalArgumentException - ネームスペース文字列が正しくないとき。
public static java.lang.String get()
カレントネームスペース設定またはnull(設定されていないとき)返す。
もしカレントネームスペースが設定されていなければ、呼び出し元は ネームスペースに関連したすべてのサービスで空のネームスペース(“”)を使うべきである。
public static java.lang.String getGoogleAppsNamespace()
このリクエストのGoogle Appsドメインを返す。または、空の文字列を返す。
public static void validateNamespace(java.lang.String namespace)
ネームスペース文字列の妥当性を検証する。
Throws:
java.lang.IllegalArgumentException - ネームスペース文字列のフォーマットが正しくないとき。