Help us understand the problem. What is going on with this article?

今年も1月終わったし DCI の話しようか

More than 5 years have passed since last update.

いままで色々 Rails 向けに DCI を実現する gem を作ってきたわけですが(Dicer / BluePrint)、今年もまた新しく考えなおして Rails 向けに DCI を実現する gem を書きました。毎年毎年ほんとよくやりますね。

今年は何気なく作り続けて、いままで活用されていなかった uninclude という gem をついに使って DCI をやってみました。

uninclude にてついては特に解説することもないというか、名は体を表すということで『#unextend#uninclude を Ruby で使えるようになる』という gem です。Refinements などでも実現可能なのですが、Refinements はファイルごとだったりでスコープがわかりづらくなるので使っていません。

RockMotive

2015年の DCI on Ruby は RockMotive です。名前は D.C.I. というバンドのシングル Rock Motive から取っています。iTunes ストアで配信しているので、気になる方は聴いてみるといいです。ちなみに RockMotive はこれを聞きながら開発しました。

RockMotive は Rails を拡張するというより、 Rails に新しい種別を追加します。既存の挙動に何か手を加える訳ではありません。Rails は標準で app 以下に models / controllers / views / assets を用意しますが、これに加えて RockMotive は roles と interactions を追加します。

過去作である BluePrint も同様でしたが、BluePrint の Context は対象の『クラス』に対してロールを指定していたのに比べ、RockMotive は対象の『インスタンス』に対してロールを指定します。

わかりにくいかと思いますので、簡単なコード例を示します。

BluePrint によるロールの指定は以下の様に行われます。

class DealContext < BluePrint::Context
  cast User, as: [Shopper, Seller]
end

売買する際、ユーザーは『購入者』と『販売者』に分かれます。が、BluePrint ではクラスに対する拡張しか行えない為、正確に各インスタンスごとの役割を振ることはできません。

対して、RockMotive によるロールの指定は以下の様に行われます。

class DealInteraction < RockMotive::Interaction
  def interact(shopper, seller)
  end
end

やったぜ!!

落ち着いて話しましょう。『これでいいのか?』と思われるかもしれませんが、これでいいです。これで指定はすべてです。何が起こっているのかを詳しくお話します。

基本は action_args から着想を得ています。 action_args はコントローラーのアクションメソッドの引数名に応じて、 params から適当な値を引数として渡してくれます。同様の事を RockMotive でも行うことで、多くのコードが省略されています。

RockMotive は #interact メソッドの定義がされた際、引数などを考慮し適当なロールを検出、割り当てます。この場合は shopper 引数には Shopper もしくは ShopperRole のモジュールを、seller 引数には Seller もしくは SellerRole モジュールが対応します。

これにより、適切に各インスタンスが『持つべき』振る舞いを持ち、『持つべきでない』振る舞いを持たなくなります。もし、そのように振る舞ってはならないような挙動をさせた場合起こるのは NoMethodError です。

RockMotive によって書かれるコード

すこし、踏み込んだサンプルを提示しましょう。『あるアイテムを売買する』というシーンです。

まず、必要な ActiveRecord モデルを定義します。このような構成になるでしょう。

Gliffy Diagrams - untitled 2015-02-01 10-39-15.png

コード上はこのような表現になるでしょう。

class User
  has_many :item_ownerships, class_name: 'Item::Ownership'
  has_many :items, through: :item_ownerships, source: :owner
end

class Item::Ownership
  belongs_to :item
  belongs_to :owner, class_name: 'User'
end

class Item
  has_many :ownerships, class_name: 'Item::Ownership'
  has_many :owners, through: :ownerships
end

各アイテムの値段はアイテムごとに固定としましょう。ユーザーはアイテムの所有権を通じてアイテムを所持しています。なのでこの際、正確に販売される物は『アイテム』そのものではなく、『アイテムの所有権』です。

まずは DealInteraction にそのまま起こるであろう出来事を記述していきましょう。

class DealInteraction < RockMotive::Interaction
  def interact(shopper, seller, ownership)
    item = ownership.item

    shopper.pay(item.price) # a.
    seller.get(item.price)  # b.
    shopper.get(ownership)  # c.
  end
end

3つの出来事が起こっています。 a. 購入者は金額を払った、 b. 販売者は金額を受け取った、 c. 購入者が所有権を得た。これら3つの出来事を、各ロールに実装していくと、以下のようになります。

module Shopper
  def pay(price)
    self.points -= price
  end

  def get(ownership)
    ownership.owner = self
  end
end

module Seller
  def get(price)
    self.points += price
  end
end

どうでしょうか?特に明瞭だとは感じませんでしたか?残念です。

このコード例では、あえて Shopper#getSeller#get のメソッド名を重複させています。これは、素の ActiveRecord で実現することはできません。やるなら #get_points とか #get_ownership とかのメソッド名を編み出すことになるでしょう。僕はもうそういうコンピューター様に大変気を使ったメソッド名を編み出すのに疲れました。#get でいいじゃない。

これはもちろん極端な例です。通常はもっとほかのやり方があると思います。ですが、僕の考える限りこれはわかりにくいコードではありません。このやり方は、『引数名できちんと意味や役割を表す』ということを強く推奨するようになります。user_a / user_b のような引数名では RockMotive は適切なロールを与えてくれませんからね。

また、これにより副作用的に、モックやスタブによるテストが簡単になります。必要な振る舞いは各 Interaction 内で与えられますから、渡すものとしては正確な User のインスタンスでなくとも、同じような属性を持つシンプルなデータクラスで構わないのです。

今年の DCI によって得られた知見(ポエム)

去年はちょっと忙しかったのでほとんど DCI できていなくて後ろめたい気持ち。もっとデータやコンテキストやインタラクションのことを考えなくては命が危ない。DCI 、もはやなんらかの病として捉えていて、なんとかしてみんなに DCI 大好きになってほしいという強い欲求だけがある。

DCI のことばかり考えていたので、最近どういう言語やパラダイムが流行っているのか全然わかってないけど、あんまりそういう話題を聞かないので寂しい。DDD とかも最近あんまり目にしなくなった。最近その手の話でたくさん目にするのは JS 界隈の MVVM とかの話だけど、あれも難しそうな話題である。特に環境の実装状況にべったりで厳しいみたいなのは、 #unextend が大抵の言語にないせいで机上の空論となっている DCI に通じるものがあって感慨深い。頑張れ MVVM 。

オブジェクト指向ちゃんとやろう!みたいな話、今でもどこかでされているのかもしれないけど、そういうのはもう一般教養とかのレベルとして認識されているのかもしれない。そうだとしたら悲しいことだと思う。一般教養化するというのは、広く知られることでもあり、深く知らなくていいことだと見切りを付けやすくなることでもあると思っていて、そうなるとあんまり悩んだり考えたりしてもらえなくなるので寂しい。

今年も DCI を頑張っていこうと思います。

rosylilly
Ruby と Javascript を少々
http://aduca.org
cookpad
料理をするユーザーさんをテクノロジの力で笑顔にする
http://cookpad.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした