DCI アーキテクチャという設計についての考え方がある。数年前から scala 界隈で盛り上がっていた記憶があるが、最近は ruby/rails 界隈でも盛り上がっている模様。
先日の札幌 ruby 会議で角谷氏が発表を行っている。
rubykaigi http://sapporo.rubykaigi.org/2012/ja/schedule/details/79.html
スライド http://kakutani.com/20120916.html#p01
そこからたどって以下のような資料があるのも発見した。
objects on rails (書籍の無料公開)
http://objectsonrails.com/
Clean Ruby
http://clean-ruby.com/
書籍サイト。ベータ版書籍が購入可能。$42なので電子書籍にしてはかなり高い。
DCIの講演 togetter
http://togetter.com/li/3335
色々disられていて面白い
私の意見も混ぜてざっと紹介してみます。
まず大元の問題意識としては、rails のアーキテクチャ(MVC)のレイヤ分割だけでうまくいくのはとても小規模なアプリのみということ。rails のレールに沿って素直にコードを書いていくといつのまにか super fat model. super fat helper が出来上がる。一つのクラスに大量のメソッドがある状態。これはC言語のように function が flat になっている状態と差がなくなるということだから非常に良くない。
従って、コードを責務などによって適切にレイヤー分割、クラス分割しないといけない。そしてそのレイヤー分割の判断根拠なるのはまずは「ビジネスロジック」や「ユーザエクスペリエンス」になる。
これらの「ドメイン」の境界に沿うようにプログラムの境界が設定されるのが自然なプログラム。
ビジネスロジックを理解せずにクラスやレイヤーの分割をするというのは大局では良い結果にならない。
そういう意味で、例えばデザインパターンのような技術者だけの道具が設計の初期に出てくるのはおかしい。技術者はえてしてそういう技術話が好きなものだが、ラムダとかデザインパターンとかmix-inとか、ビジネスロジックの出てこない道具は「syntax上」の道具でしかなく、これはつまり局所最適化にしかなりえない。それを全体レベルの最適化に使ってもうまくいくはずがない。こういう道具は局所戦でしか使ってはいけない。
まずはビジネスロジックやユーザエクスペリエンスに基づいて大枠の設計がなされ、局所的な最適化に各種ツールが活用される。その優先度が逆転してはいけない。(xxパターンに合致させるためにビジネスロジック上歪なクラス構成になってしまうなど。)ビジネスロジックに沿った境界の中を局所的なツールを使ってさらに分割するということは許されるが、プログラマの玩具によってビジネスロジックの境界が捻じ曲げられることがあってはいけない。
そしてビジネスロジックやユーザエクスぺリエンスに基づいた設計を行うために何を学ぶべきか?
というのがこのDCIや以前紹介した(かな?)DDD(Domein Driven Design)だ、と考えている。
大事なことはコードが「適切に」分割されることであって、分割されたコードをいかにruby 的に楽にmix-inするか?みたいな技術話(こういうのが局所最適化)は、コードを気持ち良く書くという短期的な幸せにとって大事ではあるが、長期的な幸せ(メンテナンス)にとっては影響度が相対的に低い。
色々大口たたいたけど自分も勉強中。。。
(追記)
そもそものDCIについて何もかいてなかったな。。。まぁ最近は紹介がたくさん転がっているのでいまさらですが、簡単に説明してみます。
DCI は上に書いた super fat model を解決するための方法、として紹介されていることが多いと思います。rails で thin controller を突き詰めると model がfatになる。大きなアプリになればなるほどビジネスロジック上の中核のモデルに大量のメソッドが生えてしまう。一番変更が激しいモデルが一番fatになっていく。そういう問題が起きます。
モデルが fat になると何がまずいか?コードの構造が読み取りづらくなり、他のメンバーが初めてコードを読んだ時に辛い思いをする。似たようなメソッドがたくさんできる。似たようなメソッドがたくさんできた時に、最初に作ったメソッドには active のようなシンプルで一般性の高い名前をつけてしまって困る。似たようなメソッドがたくさんあってよく分からなくなって、同じ名前のメソッドが定義されたり、同じ意味のメソッドが定義されたりする。などなど。
で、DCIではこれをどう解決しようとするのか。基本的な方針としては、モデルが使われるコンテキストごとに必要なメソッドを分類してモジュールに切り出して、必要なときに必要なモジュールを include しよう、ということになります。例えば入力ページを書くときだけに必要なメソッド、というのはよくあります。保存処理をするときだけ必要なメソッドなどもよくありますね。そういうものを models/user/input_role.rb や models/user/save_role.rb などのように別ファイルのモジュールに切り出して必要なときに include しましょう。とういことです。
えー、いちいち include するの面倒だよ、という人のために、DCI ではコンテキストという「場」を設けて、コンテキスト内に model が入ってきた時にそのコンテキスト用の role が自動で include されるような仕組みを作ろう、と言っています。ここについては上で少し局所最適化だ、とか吠えたように、私はそれほど重要ではないと思います。逆にこの仕組みがDCIだ、仕組みをどうやって作ろうか、みたいなところに話のフォーカスが写ってしまうとDCIの本質を見失った議論になると思います。
さて、ではmodelとroleを分けるとして、何を role に追い出すべきなのか?何が model に残っても良い、modelにとって本質的なものなのか?本来はここが議論されるべきポイントのはずです。この方針が決まる前に module が自動で include される仕組みが作られるというのは順序が逆だと思います。まぁ、ここはこれからじっくり時間をかけて合意が取られていくので。今の時点で明らかなものとしては
role に追い出すべきもの
- 特定の場面で一回のみ使うようなメソッド。例えば退会処理を実行するメソッドなど。
- view でのpresentationに関わるメソッド。
- 一時的に association の動作を変えたい場合などに上書きする
model に残すべきもの
- よく使うビジネスロジックを表現した scope。例えば、現在有効な会員データはregistered_on is not null and left_on is null だ、など。
- model の状態を判定するようなメソッド。上のscopeをメソッドで表現したようなもの。active? とか left? など。
- association
など。validation をどちらに置くべきかが議論の余地があると思う。save するときには必ず validation が走って正しいデータが保たれなければいけない、という場合は model に置くのが良いと思うし、業務アプリなどで、どうしても外部からの取り込みデータなどvalidationに通らないようなデータを格納しておかないといけない場合、常に validation が走ると batch 処理などがうまく走らなくなる。save :validate => false を使えば良いのだが、そういう validation を必要としない save がたくさんある場合は難しい。
最初の方で吠えていた局所最適化とかの話は↓でも似たようなことを書いてます。
コードの重複を排除するのもほどほどに!