2009/10/13
■[programming][GAE]RDBからGoogle App Engineのデータストアに乗り換えるときのつまずきポイントとか実例とか
先週末は、某温泉街に籠もってGoogle App Engine(GAE)を色々いじって遊ぶという2泊3日の合宿に参加してました。合宿と言っても「GAE」以外に特にテーマも決まって無くて、みんな適当に好きなことをやってたんですが、僕はGAEの初心者なので、
「リレーショナルデータベースのために書かれたテーブル定義を、Google App Engineに置き換えようとしたらどれくらい大変なのか?」
をテーマに決めて、下の本を読みながらコードを書いたり消したりして遊んでました。
----
■ 先にまとめ
まだ勘違いも多そうですが、今回分かった「リレーショナルデータベースに慣れた人のつまずきポイント」を先にまとめておきます。
GAEのデータストアでは、複合主キーのある表を作れない。
- 例えばJDOだと、@PrimaryKeyを複数のインスタンス変数に付けるとコンパイル時に例外が出る。
- ちなみに、主キーのない表も作れない。こちらもコンパイル時に例外が出る。
一意なIDを割り当てるためのカウンター(オートインクリメント機能)が必要なら、自前で実装する必要がある。
- トランザクション処理を使うことで、このようなカウンターを実装できる。
- JDOインターフェイスでは、valueStrategyにIdGeneratorStrategy.INCREMENT(なんかそれっぽい)を選べるが、GAEでは使えないらしく実行時に例外が出る。
UNIQUE制約に相当する機能もない。恐らく、自前で実装することもできない?
- UNIQUE制約は、自前で定義することもできない、ような気がする……。
- GAEのトランザクション処理は参照したデータに対する排他処理のため、新しく追加する行の値に対する制約にはなり得ないのではないか。
多対多の関係を表現しにくい。
- リレーショナルデータベースに慣れていると、多対多の関係を表現するときは、関係を表す表を新たに追加したくなってしまうけど……。
- GAEでは複数の表をまたがるトランザクションは基本的に作れないので、途中で処理に失敗した場合の例外処理を書くのがすごく面倒。
- 当然、JOIN処理なんて無い。
- 多対多を実現する方法としては、一方の表に、もう一方のIDのリストを格納する列を用意すると楽。GAEでは、「リスト内のいずれかの値がAに一致する」といった検索クエリを「listItems == :A」のような形で記述できる。
JDOとJPAのどっちを使えばいいのか分からない。
- JPAを選んでもリレーショナルデータベース固有の機能は使えないので、JDOを使っておけば良いような気がする。でも、情報がなくてよく分からない……。
以下は、上のつまずきポイントに気付くまでに、実際に書いたり消したりしていたソースコードの話です。文章の最後にソースコードも置いておきましたので、興味のある方はダウンロードしてみてください。
----
■ 具体例
最近作ったAndroidアプリ(Where's My Info?)のテーブル定義がシンプルだったので、これをGAEのデータストアに置き換えることを試みました。GAEのデータストアへのアクセス方法は以下の3種類があるのですが、今回は最も情報が多いJDOを使っています。
- JDOインターフェイス(Java Data Objects、汎用的な仕様)
- JPAインターフェイス(Java Persistence API、RDB向け)
- ローレベルAPI(Key-Valueで値を保持するAPI、高速だが低機能)
まず、元にしたAndroidアプリのテーブル定義は、以下のようなものです。個人情報(information)と情報提供先(knowers)の関係を、主キーのないinformation_knowersテーブルで表現しています。
これを、GAEのデータストアで定義すると、以下のようになります。GAE上で扱う表のことを、ここでは「エンティティ」と呼んでいます。
こうして見ると、リレーショナルデータベースでの定義と比べて、やや汚くなってるのが分かります。以下は、リレーショナルデータベース版との違いです。
- Knowerエンティティの列に、関係するInformationのIDのリストを追加。
IDを一意に割り当てるためのカウンタを追加。
- 主キーが必須なので、ダミーの主キーを定義している。
- 今この資料を書きながら思ったけど、IDを一意に割り当てるためのカウンタ類は、1つのエンティティにまとめてしまえそう……。
で、最後にこのアプリを、ユーザ毎に別の領域にデータを格納できるように拡張すると、データストアの定義は以下のようになります。
- 「110487681081245093660」というのは、Googleが提供するアカウント認証サービスから取得できる、ユーザ毎に一意なユーザID。
InformationエンティティとKnowerエンティティのキーには、ユーザID + "/" + ユーザ毎に一意なIDを使っている。
- ユーザIDとユーザ毎に一意なIDを組み合わせた複合主キーが使えたら、ホントはそっちの方がよかったんだけど……。
- クエリとインデックスの方法を使って前方一致検索をすることで、特定のユーザに関係する列のみを取り出すことができる。リンク先にあるのはPython版のコードだが、Java版でも同じ方法が使える。
実例とソースコードを、それぞれ以下の場所に置いておいたので、もし興味があったら覗いてみてください。
実例(注意:管理者=僕にはデータ丸見えなので、ホントの個人情報は書かないで!)
http://wheres-my-info.appspot.com/
ソースコード(上の実例で動いてるのと同じものです)
WheresMyInfoWeb.zip
----
■ 感想
Eclipseのプラグインを使ってると、Google App Engineのソースコードを書くのも、本番環境にデプロイするのもすごく簡単です。でも、このデータストアを使って大規模なコードを書くのは、正直言って全然出来そうな気がしません。
こんなのを使ってあんなgmailみたいな巨大アプリを書いてると思うと、やっぱりGoogleの連中はイッちゃってるよ、あいつら未来に生きてんな……と思っていたら、今回の合宿の参加者から「Googleの中では、BigTableに(GAEには含まれてない)Chubbyを組み合わせて使ってるんじゃない?」との指摘が。そんな、ひどい……。
また、この日記に無関係と判断したコメント及びトラックバックは削除する可能性があります。ご了承ください。