Rails勉強会@東京第5回に行ってきた。前回はプロジェクトが既に遅れ気味にて参加できなかったので2ヶ月ぶりとなる。
小雨の振る中、秋葉原駅前というか、雨を避けてダイビル前に寄ったあたりに集まった。怪しい集団が飲物片手に集まっているので、ダイビルの守衛さんに「敷地内飲食禁止だよ」と怒られる。でも、どこまで公道でどこから敷地なのか不明。
今回は勉強会・懇親会ともに参加者が過去最高ということである。大阪の勉強会の大規模さと本格さには及ばないけれど、東京も盛り上がってきた。
毎度おなじみ、前半・後半に分けてそれぞれオープンスペース形式を採った。
前半
3つのセッションに分かれた。
- Rails 1.1の新機能
- 『たのしいRailsレシピ』(仮)打ち合わせ
- AWDwR chap.2を読む
私は、「『たのしいRailsレシピ』(仮)打ち合わせ」に出た。ソフトバンク・クリエイティブから出版予定のRailsレシピ本のネタ出しである。もろはしさんと高橋会長が著者ということでok? 著者・編集者のみなさまが出した章立てとネタを見ながら、みんなで「こんなレシピも欲しい」とか色々いいたいことを言って案を出していった。
Pragmatic Bookshelfの本も出るけれど、Ruby-GetTextとかスペジェネとか、日本ならではの話題も盛り沢山。標準のvalidationセットは日本における開発の要求に応えるにはまだ不足とかいう意見もあったので、その辺も載るかもしれない。
Pragmatic Bookshelfの本はAWDwRを読んでrailsを使ったことがあるぐらい層を中心に応用技を教えるのに対して、こちらのレシピはRubyについてもあんまり知識は要求せず、web開発の経験があるとかそれぐらいの知識を前提に、railsのあれこれを易しく紹介していく。
私ゃさんざん色々言わせてもらったけれど、中でも特にお願いしたいのは「高橋会長の実演写真付き『正しいポーズでスペシャルジェネレーションするには』」。
Railsはフレームワークがカバーする領域が広いし、比較的使ってる層であるはずの勉強会常連でも、他の人のレシピ提案を聞いて「それ知らなかった」というのが結構あった。とても勉強になった。出席者がアクティブに発言しすぎて時間内ではインストール、ジェネレーション、ActiveRecordまでしかチェックできず、残りは懇親会にて行った。入門的であることを配慮しながらも非常に内容が濃いので、出版が楽しみである。
今秋に出版予定ということなので、皆さん買いましょう。勉強会@東京の知恵が詰まっています。
他のセッション
- Rails 1.1の新機能
- Cascades Eager Loadingすごいらしい。pagenationで使える。rakeコマンドが変わつたり追加されたりしたらしい。
- AWDwR chap.2
- 訳が素晴らしいと。声に出して読みたいRailsと。本当はpart 2のつもりで提案されたのに、間違ってchap.2と題してしまったためにすぐ終わってしまったらしい。
後半
4つのセッションに分かれた。
- 俺と一緒にRails Recipe 28を読まないか
- AWDwR chap. 20 (Action Web Service)を読む。
- Gruffライブラリ比較
- RJSさらにくわしく
私はActionWebServiceセッションのオーナーになった。数日前に終わったプロジェクトの方で、LLによるSOAP実装の候補としてRailsのActionWebServiceも評価した。そのときのレポートも公開してある。
報告のまとめ
まず、レポートを下敷にそのときの評価結果を報告。WebServiceについて、実際の開発経験はない人もいたので、その辺を軽く捕捉しながら報告した。 下記はその内容。前述のレポートも併せて御覧ください。
- 開発規模と期間、システム負荷から考えてLLを選択
- Perl5ならもちろんCPANを見る
- PHP5なら標準関数とかいろいろ有るけれど、高機能なのはPEARのSOAP(beta)
- Rubyだとsoap4r
- でも、これらは動的型のため、型情報を補わないとSOAPにうまく載せられない。
- そもそもLL界隈があんまりXML好きでないのも手伝って、バグもありがち。
- 互いの生成するメッセージをパースできなかったり、非互換性もある。Axisに対する互換性は意識していてもLL同士はあまり意識していないのかも。
- PEARのSOAPライブラリとか、ソース以外にまともなドキュメントが無い。
- WSDLを手で書くのは死ぬ。
- ActionWebServiceは型補足のためにDSLを取り入れて、比較的簡単に書けるようになっている。
- メソッドコールの方法としてのSOAP/XML-RPCと割り切って、WSDLの柔軟性やら何やらは端折る。その割り切りのセンスによって、現実の開発で使い易くなっている。
- WebService用のscaffoldで、自動生成されるフォームを使ってブラウザからWebサービスをcallできるのは大きい。
- しかも、request, resposne, return valueがきれいに表示される
それからAWDwR chap.20を順に読んでいった。
- ActionWebService、略してAWS
- 同一のコードでSOAP/XML-RPC両方をサポートするサーバーを書ける
- SOAP/XML-RPCの完全な実装ではない。あくまでも、これらのプロトコルを、メソッドコールを外部システムに公開する手段と割り切っている。
- Apiクラス内で、DSLっぽく外部インターフェースを定義
- Controllerまたは専用のServiceクラス内で外部インターフェースを実装
API定義DSLの仕組み
動的型言語において、どうやってSOAPなどに必要な型情報を捕捉するかは問題であるが、ActionWebServiceではDSLで解決する。
一揃いののAPI(SOAPでいうportに対応)はActionWebService::API::Baseのサブクラスで表現される。このサブクラスの中でapi_methodクラスメソッドを利用してメソッド型定義を宣言する。
class HogeApi < ActiveRecord::API::Base
api_method :foo
api_method :bar
end
メソッドの引数や戻り値やの型はapi_methodにオプションを付けて表現する。利用できる型は次の通り
- XSchemaの基本データ型の一部: これらはAWSのDSL内では
:string,:int,:datetimeのようなシンボル値で表現する ActiveRecord::Baseのサブクラス: これらはDSL内ではClassオブジェクトそのもので表現する- 構造化型: 利用できる型をメンバーとする任意にネスト可能な構造体。
ActionWebService::Structを継承したClassオブジェクトで表現する。 - 配列型: 利用できる型をメンバーとする配列。DSL内では型表記を唯一のメンバーとするような配列で表現する
# 整数計算API
class CalcApi < ActiveRecord::API::Base
# 加算
api_method :add,
:expects => [:int, :int],
:returns => [:int]
# 減算
api_method :sub,
:expects => [:int, :int],
:returns => [:int]
# 約数を列挙
api_method :divisors,
:expects => [:int],
:returns => [[:int]]
end
Apiクラスは外部にAPI定義を示すという意味で、Javaのインターフェースに近い存在である。Javaで書くと大体こんなインターフェースと等価である。
interface CalcApi {
int add(int param1, int param2);
int sub(int param1, int param2);
int[] divisors(int param1);
}
ここで、AWSのAPI定義にはパラメータ名が含まれていないことに気づく。パラメータ名を設定するには単項のHashを使う。
# 整数計算API
class CalcApi < ActiveRecord::API::Base
# 加算
api_method :add,
:expects => [{:lhs => :int}, {:rhs => :int}],
:returns => [:int]
# 減算
api_method :sub,
:expects => [{lhs => :int}, {:rhs => :int}],
:returns => [:int]
# 約数を列挙
api_method :divisors,
:expects => [{:base => :int}],
:returns => [[:int]]
end
これで、次と等価となる
interface CalcApi {
int add(int lhs, int rhs);
int sub(int lhs, int rhs);
int[] divisors(int base);
}
言語内に型定義の専用語彙を持つ静的型言語に比べればいくらか落ちるが、それでもRubyのDSLを生み出す力を最大限に引き出して、動的型言語としては比較的綺麗にAPIを定義できていると思う。増してWSDLを手で書くのに比べれば100倍楽だ。
上の例では構造化型などを使っていないが、ActiveRecord::Baseのサブクラス、つまるところRailsアプリケーションにおけるModelクラスをそのまま返り値にできるのは便利だ。内部モデルとAPI側のデータ構造が本質的に同一である場合、わざわざ^「詰め替え」をしなくて済む。
Javaなら内部モデルが「たまたま」外部インターフェースに表れる構造型と同等な場合でも、疎結合性を保つために別のinterfaceを定義することを検討するだろう。しかし、ここで動的型言語とWebServiceの利点が活きて、メンバー構造さえ保てばそれが実際にどういう型であるかは問題にならないので(Duck typeだね)、Modelクラスをそのまま外に出しても結合は密にならない。いくらRailsが密を是とするフレームワークでも、外部システムと密なのは一般には嬉しくないものね。
AWDwRには明記していなかった気がするが、前掲の私のレポートにある通り、ActiveRecord::Baseのサブクラスは引数には指定できない。指定したら"ActiveRecord model classes not allowed in :expects"と言われた。これは残念。
generation
上のようなAPI定義の雛型を作るためのgeneratorが標準で用意されている。次を呼ぶ。
$ script/generate web_service ApiName [method_name, ...]
これにより、ApiNameApi(API定義クラス)、ApiNameController(API実装クラス)、ApiNameApiTest(API機能テスト)が生成される。
$ script/generate web_service Calc add sub divisors => create app/apis exists test/functional create app/apis/calc_api.rb create app/controllers/calc_controller.rb create test/functional/calc_api_test.rb
生成されたあとは、
- メソッド定義に
:expectsと:returnsを設定 - コントローラーのメソッド実装にも、対応するパラメータを指定
- メソッドの中身を実装
:expectsと:returnsのほうはよいだろう。次に、生成された段階ではCalcControllerはこんな感じなので、
class CalcController < ApplicationController
wsdl_service_name 'Calc'
def add
end
def sub
end
def divisors
end
end
これに、パラメータを足して実装する。
class CalcController < ApplicationController
wsdl_service_name 'Calc'
def add(lhs, rhs)
lhs + rhs
end
def sub(lhs, rhs)
lhs - rhs
end
def divisors(base)
(1..Math.sqrt(base).to_i).inject([]) { |divs, candidate|
div, mod = base.divmod(candidate)
if mod == 0
divs << candidate
divs << div unless div == candidate
end
divs
}
end
end
これでおしまいである。ポイントは、パラメータをActionController::Base#paramsではなく、普通に受け取ること。
scaffold
素晴らしいのは、scaffold機能である。上のControllerクラス定義に次の宣言を足す。
web_service_scaffold :invoke
:invokeというのは任意に指定できる、scaffold画面にアクセスするためのaction名である。これで、script/serverを起動して http://localhost:3000/calc/invoke にアクセスしてみよう。
メソッドの一覧がででくる。そして、試しにaddメソッドを選択してみるとパラメータの入力フォームが出でくる。
これをsubmitするとrequest, resposneのXMLダンプ付きで結果が表示される。
結果が配列でも同様。
もちろん、実際の開発ではサブコンポーネントの単体テストをまずしっかりやってからなのだけれど。でも開発してるAPIを試せるいい感じのGUIが、こうやって只で手に入るのは嬉しい。Webサービスを開発した経験があればいいもの使ってる感があるだろう。
このscaffoldingの仕組みは純粋に開発中のテストのためのものなので、運用時にはweb_service_scaffold宣言を削って外してしまう。このあたり、ActionViewなんかのテンプレート生成よりも"scaffold"っていう意味合いがより明確だ。本当に構築作業用の「足場」なのね。
Dispatchingとか
今は、Controller内にベタにAPIを実装してしまった。でもこの方法には一つ問題がある。1つのweb-serviceエンドポイントURLに1つのAPIしか配置できないのだ。RailsのRoutingの仕組みでは、1つのPATH_INFO componentは1つのコントローラーにマップするしかなくて、上のやりかたでは1つのコントローラーには1つのAPIしか実装できないからだ。
ジェネレーターがデフォルトで生成する上のようなやりかたは"Direct dispatching mode"と言って、AWSが提供する3つのやりかたのうちの1つである。この他に"Layered Dispatching mode"と"Delegated dispatching mode"がある。
"Layered dispatching mode"では、1つのコントローラーで複数のAPIをサポートできる。変更点すべき点は、
- サポートするそれぞれのAPIにつき、
ActionWebService::Baseを継承したクラスを作成する。コントローラー内でAPIを実装する代わりに、このクラスで実装を提供する。 - コントローラークラスで"
web_service_dispatching_mode :layered"を宣言する - コントローラークラスで、API名と
ActionWebService::Baseの具象インスタンスの対応を宣言する。
例えば、controllerクラスが
class CalcController < ApplicationController
wsdl_service_name 'Calc'
web_service_dispatching_mode :layaered
web_service :calc, CalcService.new
end
で、serviceクラスが
class CalcService < ActionWebService::Base
def add(lhs, rhs)
lhs + rhs
end
def sub(lhs, rhs)
lhs - rhs
end
def divisors(base)
(1..Math.sqrt(base).to_i).inject([]) { |divs, candidate|
div, mod = base.divmod(candidate)
if mod == 0
divs << candidate
divs << div unless div == candidate
end
divs
}
end
end
配置とか
- 構造体型の定義は、これはAPIの一部であると考えてapp/apisの中が良いのかな。
- Serviceクラスの定義は、私はcontrollerに近い存在だと思うから(あと、generatorが作ったcontrollerのファイルをコピーして書いたので)app/controllersに置くのがいいんじゃないかと思う。
- app/servicesっていうのを作って
$LOAD_PATHに追加するのもアリではないかという意見も
公開とか
上の例で、 http://localhost:3000/calc/service.wsdl にアクセスすればSOAP用のWSDLが手に入る。生成されたものを見てみてほしい。これを手で書くと大変だけれど、DSLの力で上のAPI定義だけで生成されるのだ。
XML-RPCの場合は http://localhost:3000/calc/api がエンドポイントとなる。
テストの自動化とか
AWSのテストをサポートする機能もしっかり提供されていて、generatorでweb_serviceを生成した時にはテストケースのスケルトンも生成される。
controllerのテストでgetやpostを呼んでアクションのの挙動をテストできるように、テストケース内でinvokeというメソッドを呼ぶことで公開されたメソッドの挙動をテストできる。
クライアント機能とか
一応、AWSにもクライアント機能はある。ActionWebService::Client::SoapとActionWebService::Client::XmlRpcだ。しかし、newにAPI定義クラスを渡さないといけない。つまり、外部システムのインターフェース定義をAWSのDSL形式に変換しないと理解してくれないのだ。外部システムのためにわざわざAPI定義を書き下すのは面倒なので、この点だけはsoap4rの方が便利そうに思える。
AWSのクライアント機能はRails同士をWeb serviceでつなぐ場合のため、ということだろうか?
script/generate wsdl2api ができてもいいのにね。という御意見あり。ごもっとも。soap4rのwsdl2ruby.rbのラッパーでもいいから、generatorが欲しい。
やってみた
そんな感じで、みんなで実際にAWSを試してみた。
- scaffoldのパワーに感嘆する。
- Calcを作ってみた。
- web service未経験だった人が、既存のrailsアプリケーションの機能を20分でWebサービスとして公開できた。
- 駅名検索API凄い。
- AWSを流行らせれば有用なAPIがどんどん公開される予感
結論
Rails使いの皆さん、初めてscaffold generatorを使ってみたとき、初めてスペジェネを使ってみたとき、初めて色つきログを見た時のあの気持ちを思い出してください。
- 「おぉすごい」
- 「ここまでやるか。アホちゃうか」
- 「いいものつかってるぞ」
- あの気持ちをWebServiceの世界で今一度味わえます。
大規模開発ならSOAP周りも充実しているしJavaや(C++や)ASP.NETが現実的でしょう。でも、中小規模のWeb Service開発をアジャイルにやるならActionWebServiceでしょう。
他のセッション
俺と一緒にRecipe28を読まないか
かくたにさんのセッション。Pragmatic BookshelfのほうのRails Recipesのβ版を読んだらしい。レシピが増えたため、かくたにさんの想定していたレシピは当日の最新バージョンではrecipe 43になっていたとか。
Rails 1.1で導入されたintegration testはすごそう。
- unit testはmodelクラスをそれだけでテストする。
- functional testはcontrollerを直接呼び出すことでcontrollerがmodelをコントロールする様子をテストする。
- integrationテストは、Routingまで含めたRailsフレームワーク全体の挙動を呼び出して、controllerをまたがった処理、セッションの状態に依存する処理をテストする
しかも、Integration testのテストケースは、ほとんど英文を読んでいるかのような自然なDSLで書かれるらしい。Bob.login.as.administratorとか。
Gruffライブラリ
オーナーはもろはしさん、だったかな?
GruffやらRMagicやらCSS-graphやら、いろいろ面白そうなグラフライブラリを比較して、Railsで利用することを検討したらしい。途中でささださんが乱入して盛り上がったとか。
RJSさらにくわしく
前回のRJSセッションを引き継いでさらに詳しく、らしい。
- RJSは、Rubyコードを書くとJavaScriptを生成して、そのスクリプトがDOMを通じてブラウザの画面をいじくる仕組み。
- RJSを使ったページの挙動を調べるには、JavaScriptが弄った後のソースを見られないと不便。そのためにはFirefoxのView generated sourceプラグインが便利、らしい。
view generated sourceってどこだろう? addons.mozilla.orgやgoogleでは見付からなかったんだけど。このブックマークレットだろうか。
懇親会=宴会
一次会では、昨今Rubyistが集まれば必ず始まるHaskell話とか、レシピ本ネタ出しの続きとかをした記憶がある。二次会の記憶がほとんどないのは何故だろう。そんなに飲んでないのに。
入門Haskellを読んで、ようやく数学概念とHaskellの概念がすっきり結び付いた。<-が∈のことだと分かってまずすっきりした。クラスはそのままクラスで良いよね。モナドはそのままモナドでいいよね。でも、モナド律がどうやってIOの挙動に結び付くのかよく分かってなかった。その辺、しっくりきた。数学の世界までやってくれば、あとはホームグラウンドだ。
とか、そんな話をしたり。Railsで20分でWikiを作ってしまった方の話を聞いてびっくりしたり。
ささださんに「『ぱるま』の続ききぼんぬ」と言ったら「そんな暇あるわけない」と言われた。
コメント
AWSセッションでは、お世話になりました。
> view generated sourceってどこだろう?
Web Developer Extension( <a href="http://chrispederick.com/work/webdeveloper/">http://chrispederick.com/work/webdeveloper/</a> )
の view generated source 機能だと思います。
この機能に限らず、相当便利です。
お疲れさまでした。
> Web Developer Extension
気づいてませんでした。ありがとうございます。
もしかしたらもしかして、20分でWikiを作った人、というのは私のことだったりするんでしょうか...。
作れると言った気はしますが、実際に20分で作ったわけではなかったんですが...。
ということで、責任を取って実際にやってみました。
まぁ一応なんとかなりました。
宜しければご覧ください。
<a href="http://techno.hippy.jp/rorwiki/?10%CA%AC%A4%C7%BA%EE%A4%EBRailsWiki+for+Windows">http://techno.hippy.jp/rorwiki/?10%CA%AC%A4%C7%BA%EE%A4%EBRailsWiki+for+Windows</a>