GoogleAppEngineでRubyアプリを動かすまで
大晦日の日、自分への挑戦・・・ってほどのことではないですが、
1日でGAEアプリを作るのに挑戦してみました
・・・と言っても、この上なくシンプルなCRUDアプリではあるのですが、
以前GAEでSinatraを動かしたときは、データのところまでは見てなかったので、
それを調べながらの作成でした
アプリ:
説明: RO的悪魔の辞典 - Angel, alone ~孤独な天使~
<追記:2010/01/14>コードを公開しました</追記>
アクセスしてもらえばわかると思いますが、異様に重いです
人によってはGAEの30秒ルールでエラーになるかと思います
その原因については後で考察しますが、
とりあえず作るまでのポイントを大雑把に
環境構築(GAEでSinatra)
基本的に以前書いたことそのままです
GAEでJRubyのSinatraを動かすまでのメモ - どっかのBlogの前置きのような
前回不明確なままだったGemfileに関してですが、
ローカルでアプリを初回起動するときに自動で作られるようです
# Gemfile # Critical default settings: disable_system_gems disable_rubygems bundle_path ".gems/bundler_gems" # List gems to bundle here: gem "appengine-rack" gem "dm-appengine" # これと gem "sinatra" # これのコメントを外す
datastoreへの保存
最初に調べたとき、データのdeleteができない的なことを見た気がして、
もう一度調べてみたところ、他にもできないって記事がありました
(後述の「おまけ」参照)
とはいえ、それらはGoogleの推奨するDataMapper経由ではなく、
もっとローレベルのAPI(appengine-api)を叩いていたので、
それが原因かな・・・とも
結論から言えば、DataMapperを経由すれば、
問題なくCRUD操作が可能でした(`・ω・´) b
class Dictionary include DataMapper::Resource property :id, Serial property :word, String, :index => true property :explanation, Text def add_explanation(exp) @exp_list ||= [] @exp_list << exp end def before_save @exp_list ||= [] self.explanation = @exp_list.to_yaml end def explanation_list @exp_list ||= YAML.load(self.explanation) @exp_list ||= [] @exp_list end def size explanation_list.size end end
本来「用語」と「説明」で1:nの定義をすべきですが、
GAEのBigTableであまり関連は使いたくないので、
内部的には「説明リスト」をYAML化して保存してます
これだと説明文を検索するとき困るのですが、
今回はさくっと動かしたかったのでこれに
用語リストを取得するときも全部取ってきてしまうのが冗長ですが、
BigTableの読み込みは速いはずなので、
大丈夫だろうという読みもありました
ちなみに、wordに対してIndexを要求してみたのですが、
datastore上ではidにのみIndexが生成されていました
XMLで明示的に作ろうとしたらwarningが出たので、
おそらくGAE側で不要と判断されたようです
ユーザ認証
今回の「悪魔の辞典」は、一般ユーザは閲覧だけで、
登録・更新・削除は私だけが可能な仕様になってます
(あくまで現段階では、ですが)
そのためにGoogleのユーザ認証APIを使ったのですが、
それでいくつか詰まったポイントが
認証後の戻り先がおかしい
http://code.google.com/p/appengine-jruby/wiki/UsingTheUsersService
ここに書かれている方法で、「/xxx」に対して認証をかけると、
なぜか「/xxx/xxx」に飛ばされてエラーになりました
仕方ないので、AppEngine::Users.create_login_url('/') としてますが、
いまいちどう直したらいいのかわかりません
管理者権限の取得の仕方が変
Appの管理者であるかは、Java等だとUserオブジェクトに問い合わせればわかります
ただ、JRubyのAPIだと、AppEngine::Users::User#admin?ではなく、
AppEngine::Users.admin?なのです
http://code.google.com/p/appengine-jruby/wiki/UsingTheUsersService
This is a separate function, and not a member function of the User class, because admin status is not persisted in the datastore. It only exists for the user making this request right now.
つまり、ログイン済みのユーザが管理者なのか、
このAPIではわからない、ということですΣ(゚Д゚)ガーン
仕方ないので、今はAppEngine::Users::User#emailで判断してます
遅延の原因
とりあえず、ここまででローカル環境での動作はOKだったので、
早速本番にUploadして動かしたわけですが、
ごらんの通り、相当遅いです(´・ω・`)
考えられる原因は以下
これを確認するには、GAEの上でSinatraのサンプルをそのまま動かしてみればいいはず
http://3.latest.parrot-test.appspot.com/(いつ消すかわからないのでリンクなし)
この程度のを動かしても重いので、
GAEでのJRubyが重たい、ということになりそうです
何度かリクエストを投げると速くなるので、
たぶんVMの起動に時間がかかっているんでしょう
さらにJRubyの変換コストが上乗せされるため、
30秒ルールに引っかかるほどの遅さになってしまう・・・と
もしかしたらRackが重たいのかもしれませんが、
RailsにせよSinatraにせよ、JRubyのRackで動くわけで、
現時点ではちょっと実用に耐えない感じです
今後のチューニングに期待ですかね
一番いいのはRubyのネイティブ対応ですが・・・
おまけ:appengine-apisを使うと削除できない原因
http://www.machu.jp/diary/20090903.html
http://blog.livedoor.jp/maru_tak/archives/cat_10010330.html
appengine-apisを使って削除できない原因ですが、
AppEngine::Datastore.delete(key) ではなく、
AppEngine::Datastore.delete(nil, key) かな・・・と
APIドキュメントを見ると、
第1引数がトランザクションオブジェクトを要求していて、
デフォルトがnilになってます
なので、delete(key)だと、引数が足りないんじゃないかと