鳩舎

レースしない

DCI を考える

追記

ということで、以下の内容はすべて間違いである可能性が高いです。

元記事

Data - Context - Interaction いわゆる DCI が最近の人気らしい。 DCI そのものの説明をこのエントリでする気はないので、 Sapporo Ruby Kaigi の角谷さんのプレゼンなどを見るとよい。

Rails の場合、 Data はまぁ ActiveRecord / Mongoid などのいわゆる MVC におけるモデル、であっていると思う。これについては何も違和感がなくて、 validation などがモデルに含まれていても僕は違和感がない。それはデータが正当かどうかを検証するロジックであり、それらはモデルが抱えるべきメソッドだからだ。

んで、 Interaction も多分普通の Module とかで問題ない。これを Role と捉える見方もあるし、Decorator Pattern の Decorator みたいなもんだろというのも聞いた。松田さんの ActiveDecorator なんかはまさに View の Context でのみ稼働する Interaction だと僕は捉えてる。

では、 Context とはなんなのか、どこにあるべきなのか、が目下の問題と懸念である。 DCI そのものは僕は概ね Strategy Pattern なんじゃないっすかねみたいな気持ちでいる(Strategy Pattern は正確には実装を取り替え可能にするもので、インターフェースが動的に変化するものではない)ので、試しに ActiveStrategy を作ってみた。なかなかうまく行ったとは思っていて、これによって実装を切り出すことができたのは有意義だなと思ったけれど、 ActiveStrategy は言ってみればすこし便利な include で、結局モデルに strategy :hoge を書いてしまっているので、モデルが太っていくことに変わりはない。無念である。

僕が嬉しい DCI な場面ってのは、つまるところこんな感じだ。

ユーザーとカートとアイテムのモデルがある。

class User
  attr_reader :name, :id
  has_one :cart
end

class Cart
  belongs_to :user
  has_and_belongs_to_many :items
end

class Item
  has_and_belongs_to_many :cart
end

アイテム画面のコントローラと、カート画面のコントローラがある。

class ItemsController
  def add_to_cart
    user.cart.items << item
  end
end

class CartsController
  def remove_item
    user.cart.items.delete(item)
  end
end

今回はごく単純な例なので、普通に Array#push みたいなことをすれば気が済むのだけれど、これがもっと条件が複雑になる(たとえばチャージしているポイント以上はカートに積めないとか)と、途端にコントローラが太り、モデルに移して事無きを得る(とともにモデルが肥満児童みたいになっていく)。

こういうコンテキストをいかに解決するか、というのが話のキモで、いわばカートにアイテムを追加するロジックは ItemsController の add_to_cart メソッドの外では必要ないし、カートからアイテムを取り除くロジックも CartsController の remove_item の外では必要ない。今は

将来的にはアイテム画面で『カートにそのアイテムがあった時だけ』はカートからそのアイテムを削除するようなユーザーフレンドリーな機能が搭載されるかもしれないし、カートに複数の商品が乗っているときの挙動などもこの例では未定義だ。今はシンプルに見えるけれど、これは仕様が詰まってくればより複雑で多様なケースで扱われるインタラクションになるだろう。

例えばログイン済みであるとか、例えば特定の URI クエリが来ているだとか、例えば特定の UA からのアクセスであるとか。

そういうもろもろの面倒なものをうまく面倒みてくれる Context を作ってくれる Gem があると、すごく便利なのかなぁ、ついでにモデルに動的に Interaction を extend してくれたりするといいよね。あれ、それって、 Chanko ...

というようなことをぽつぽつ考えています。難しい。無念。

続編はこちら