先日お邪魔した
Google I/O報告会 in 東京でApp Engineなネタを発表したりしましたが、実はApp Engineをちゃんと使ったことは無かったりします。以前にやったことはといえばアカウントを作って試しに静的なファイルをデプロイしてみて終了というなかなか悲しい感じで、きっと当時の私には必要なかったのだと思います。
が、Google I/Oでセッションに触れ、また今後betaを卒業していったり諸々の(全文検索含め)機能強化がされていくことを考えると、いくらPython 2.5という今からするとちょっぴり古い言語がメインであっても(Javaで書く気はあんまり無くて、せっかくApp Engine使うならPythonで書きたい)App Engine使いたい気持ちが高まってきた今日この頃。そんなところに丁度
ネタ振りがあったので今回はApp Engineで書いてみようという次第。ちなみにPythonはそれなりに思い通りのものを書ける程度でPythonistaではない。このへんもどんどん晒しつつ勉強していきたいところ。
やり始めてから途中丸一週間停滞という、旬が大事なプロジェクトとしては割と致命的なアクシデントもあった(これは色々言い訳してもしょうがない)んですが、サービスをさっくりとApp Engine上で作ってみました。以下はそのメモです。
----
メモ:
今回、基盤としてはtwitterの情報を利用するのでOAuthベースの認証/認可といくつかの周辺機能で構成することになる。
目的はOAuthの仕組みを作ることでもなければtwitterのAPIを知ることでもないので、既に作成/公開されているライブラリをありがたく使う。
Google App Engineで手軽にOAuthアプリを作成!(Twitterとか!) - AppEngine-OAuth
これを使い、twitter IDでのログインまではさっくりと進んだ。ちなみにApp Engineへのデプロイは結構遅めなので、どんどんコードを書き換えながら進めるには不向き。今回はtwitterの連携アプリ設定で"localhost"を許可リストへ追加し、"localhost:8080"な環境から直接twitterでの認証が使えるようにして開発を効率化した(実装としては、あまりホスト名を見て処理切り分けるのが好きでないのでLOCAL_TESTINGというグローバル変数を用意)。これに伴いOAuth用のライブラリを一部拡張した。コードはgithubあたりに置くつもりなので必要な方はそちらから拾ってもらえばいいのだけど、やってることは認証時のリクエストパラメータに'oauth_callback'を指定してlocalhostへ戻ってこれるようにしただけ。
さて、ログインまでは出来たけれどセッション情報をどのように保持しよう。ちょっと調べてみた感じ、App Engine自体でセッションの仕組みは提供されてなくて、
http://gaeutilities.appspot.com/sessionこのへんの実装を使うというのが割と定石っぽい。バックエンドにはDatastoreとMemcachedが使えるのだけど、Memcachedで利用出来る容量は公表されておらず
How much memory of Memcache is available to a Google App Engine account?あたりを読んだ感じでは、アプリでの利用方法やトラフィックに依るということでなかなか恐ろしい。ここで設計を最大限パフォーマンス寄りに倒す必要は現時点で無いので、(Memcachedに保存したほうが当然爆速なんだろうけど)ひとまず安全側に倒してDatastoreでの実装としておく。
Google App Engine Pythonでセッションらしきものを実装するを参考に、Cookieの読み書き部分+Datastoreで管理するようにした。
データ構造としては揮発性のセッションと不揮発性のユーザデータを基盤とする。
揮発性のもの:
・ユーザのログインCookie
不揮発性のもの:
・twitterのOAuthトークン
・ユーザの開始時体重、目標体重
・ユーザの日々の変化情報
・ランキング情報
RDBでのスキーマ設計と異なり、1:1なものはほどほどまとめて1つのデータモデルにする。この例外は容量がやたらデカいものを切り出すとかかな(lazy-loading相当のことをやる方法があるのか分からないため)。1:Nなものは、数量が読めて検索上困らないならJSONなりカンマ区切りなりで1カラムに集約しても良さそう。数が読めなかったり後から最新の一部のみ切りだして利用する必要があるなどの事情があれば大人しく別モデルにしておくべきだろうなぁ。M:Nなものは、一旦別モデルにしておいて集約出来そうなら後から集約するのが良さそう。RDBで言うところのJOINが必要になるケースを避けたほうがパフォーマンス的に有利なはずなのでそのように計らう。
セッションをモデルとして分割するか否かについて少々悩んだ結果、大した容量にならないことだしセッションキーもセッションデータ(これはJSONで)も永続ユーザデータ側モデルの中へ入れておくことにした。小さなサービスで、ユーザからの入力パターンがほとんどないのでデータを階層化したセッションハンドラとかは要らない。
日々の計測データも分量が知れているので同様に。けど、日々の計測データはApp Engine的な作法(コードはなるべくそのままでも多ユーザ・多データにスケールさせられるようにする)としては別モデルに切っておくのが多分正解。
さて、ここで少々ややこしいのが、twitterのOAuthトークン自体の寿命とユーザのログインCookieの寿命が必ずしも一致しないし、ユーザのログアウトがtwitterのOAuthトークン無効化を意味するわけでもないというところ。今回のサービスでは、ユーザに代わってチーム内での日々の順位をツイートする機能を実装するので、ユーザログアウト(これはkeep-logged-inで共有PCなどにおいて他の人が元ユーザに成り代わって操作するのを防ぐためのもの)後でもtwitterのOAuthトークンを維持するのが正しい実装。このへんはサービスがどのような機能/使い勝手を提供するかによって変わるとこ。
スキーマのアップデートとか後からきっと必要になるのだけど、このへんはユーザの新レコードを作成した際にきちんと処理しないと面倒なことになるかも。世代ごとにデフォルト値管理するとか結構めんどい。
まさかテンプレートでincludeが使えないなんて…部分render→結果を食わせて部分renderかぁ、このへんは半分気分の問題なので、ほどよくラップ出来ればあまり気にならないかも。
あと今のところほぼ気にしないで良いレベルだけど、OAuthベースの通信に必要なUrlFetchにはQuotaがかけられてる(回数で1日あたり657,084、容量で1日あたり4GB in/out)。このへんは必要に応じてサブアプリを登録してそいつとの間でメッセージをやり取り(当然これ自体がAPI消費するので集約性重要)することである程度回避が可能な感じ。
----
まとめというか感想というか:
・App Engineはなかなか幸せな環境っぽい
・少なくとも、ある程度の使い方は把握出来たので今後App Engine上でのアプリプロトタイプ開発は加速出来そう。というか、考えたものはどんどん出していけるようにしなきゃまずい
・今度はJavaも使ってみたい。Slim3ちょっと調べてみた感じ結構イケてる。Eclipse含めた環境としてのJavaは書いててそんなに苦痛無いしJava6対応してるし
----
今後勉強したりするnotes:
http://code.google.com/intl/en/appengine/docs/python/tools/webapp/requestclass.html
リクエスト送信されてくる内容はこのへんを参考にする。
・ライブラリ内でApp Engine特有な部分はurlfetchぐらいかな、httplib2と大差ない気がするので今度違いを調べる。というか、google.appengine.extの中身はひとまず全部見ておいたほうが後々よさそうなので見る
・Datastoreで不要になったデータをexpireさせる方法(多分タイムスタンプを用意しておいてcronで範囲指定削除)を用意する。データ容量的にも効率的にも必要
・Datastoreに所謂PKっぽいものを指定する方法、インデックスを作成する(自動作成されない場合)方法
・db.Modelからlazy-loading(一部カラムのデータだけ、fetch時に取得せず具体的コール時に取得する)の仕組みがあるか調べる