DCI を考える #2
追記
まじで鳩さんのスライドでDCIについて理解したつもりになるの危険だからやめた方がいいです。せめて d.hatena.ne.jp/digitalsoul/20… を読みましょう。DCIはエンドユーザのメンタルモデルを実装に落とし込むための設計パラダイムです
— Naoto Takai (@takai) December 27, 2012
ということで、以下の内容はすべて間違いである可能性が高いです。
元記事
これまでのあらすじ: ActiveStrategy はイマイチなアプローチだよね。
文脈によって可能な挙動が変わり、モデルは基本的にデータのみを持つことでクリーンな状態を保とうといっているのに、便利な include
を提供する ActiveStrategy はやはりイマイチなアプローチで、挙動の切り分けが容易になるのはいいことだけれど、それって今までの include 地獄から何も進化してないよね?という話から。
では文脈とは、とりわけ Web アプリケーションにおける文脈とは何かという話になってくるのだけれど、これは少々ややこしい。ぱっと浮かぶだけでもこれだけある。
- リクエストヘッダ
- Cookie
- Session
- モデルの状態
- どのコントローラにいるか
しかも困ったことにこれらはモデルやクラスをまたいで存在する文脈だ。疎結合とはなんだったのか!本当にすべてを(自動的に)カバーしようと思うならクラスを超えたアクセスを実現することになるけれど、それって binding_of_caller みたいな黒魔法の上に成り立つものじゃないかな……
では都度文脈を生成するアプローチに立ち返ってみるのだけれど、まぁこの場合だと順当に Mike Pack のブログみたいなことを考える。つまり文脈にパラメータを与えてそれが上手いこと振る舞う、という話だ。一見これはすっきりしてるし、わかりやすいアプローチだと思う。インタラクション、つまり挙動はそれらをまとめる Role が持っており、文脈(Context)は与えらた情報から適切に Role を extend したり、特定の挙動を呼び出すって寸法だ。うん、良い感じじゃないか。都合よく分離されている。どの挙動を扱うかは文脈がしっており、どう振る舞うべきかは Role が知っている。
ちょっとまって! Mike Pack も、Ruby Works の dci gem も、確かに同じアプローチをとってるんだけれど、これっておかしいんじゃないか?そもそも論じゃあないけど、少なくとも僕は Context#call
なんて、 Proc でもないものを『呼ぶ』のはごめんだぞ。それなら User#authenticate
を泣く泣くモデルに入れたほうがずっとわかりやすい。大事なのは綺麗に切り分けられていて、かつすぐ思い出せそうなことだ。サンプルをあげつらうのはお門違いだとわかってはいるが、『カートに追加する文脈(AddToCartContext)』って一体全体何を見る文脈なんだ? User#add_to_cart
なら確かにユーザーのカートに何か突っ込むんだろうし、渡すべき引数もメソッド名だけで大体わかる。僕らが書いているのは Java じゃないから、(本当に残念だけど) Eclipse は引数に何を渡すべきか教えてくれない(Rsense を使えばいいよ!というアドバイスが欲しいわけじゃない)。そのメソッドが必要なものはなるべく簡単に予測できないといけない。
とまぁ、ぐだぐだと愚痴ってもしょうがない。Shut the fuck up and write some code. にのっとって、『僕が考える最強の DCI』を作るのが一番いいことだ(たとえそれが嘲笑の的になっても)。
はい、書いた。Dicer(hosted on github) だ。ActiveStrategy 同様、まだ rubygems には乗ってない。
まだこなれていないけれど、概ね僕のやりたかったことは実現されている。
例えば Context#call
なんて書きたくない!という僕のわがままは
user.with_context(some_context).add_to_cart(item)
となった。これはなかなかわかりやすい方じゃないかな。『ユーザーは特定の状況下で #add_to_cart
を持つ』というのはシンプルだ。 Context はどこで作ってもいいし、何を渡してもいい。その文脈が状態を判断するのに必要な分だけ渡してあげればいい。
どの挙動を採用し、どの挙動を採用しないか、という判断は文脈が行う。 interaction :action, :if => guest?
なら、 Context#guest?
が false の時には action
は呼べないってことになる。そしたら NoMethodError だ。
Mike Pack らの書いた Role に相当するものは Dicer::Interaction になった。これは挙動をまとめる、って意味だ。例えば認証に必要な挙動をまとめた AuthenticateInteraction
とか。そういうもの。
dci gem や、 Mike Pack のサンプルと違うところは、 Context が挙動を呼び出しを関知しないことだと思う。文脈は『これらがどのように振る舞えるか』は知っているべきだし、それらを与えてあげていいと思うけれど、『それらがどのように振る舞うか』まで知ってる必要はないと僕は思う。
とまぁぐだぐだやってみて、とてもいい修行だったと思う。とにかく僕にとっての回答はひとつ出たわけだし。こっから先は一人で考えても埒が明かない気がしているので、 @kakutani さんや @ursm さんら、Sapporo Ruby Kaigi で DCI に触れていた人や、僕に DCI の存在を教えてくれた @lchin に話を聞きたいな。永和では Interaction を Activity として定義してるらしいけど、そのへんもどうなってるのか気になるとこだなぁ。 gem になってないのが残念すぎる。