2013の6日目の記事です。さんの「Favoriteの設計実装はパターンとして使える」でした。Railsで適切に責務を分割するということRailsはいわゆるMVCと呼ばれるアーキテクチャパターンにのっとったフレームワークであり、プロジェクトを作成するとデフォルトでmodels/、views/、controllers/などのディレクトリが作成されます。基本的にロジックを記述する場所はモデルであり、ビューには表示処理だけを、コントローラにはアプリケーション上必要な手続きだけを記述するべきであると一般的には言われています。それを忠実に実践していった結果、モデルが肥大化しメンテナンシビリティやテスタビリティが低下するという問題も多く指摘されています。さんも指摘していて、それに対するアプローチについてまとめてくださっています。この記事では少し違ったアプローチでモデルの肥大化と戦った話をしてみようと思います。DDD)というものがあります。Wikipediaには次のように紹介されています。DDD)とはソフトウェアの設計手法であり、'複雑なドメインの設計はモデルベースで行うべきであり'、'また大半のソフトウェアプロジェクトではシステムを実装するための特定の技術ではなくドメインそのものとドメインのロジックに焦点を置くべき'とする。いった高位の概念と実践について多数述べられている。language(共通言語)を形成するべきである、という考えである。同書は多層アーキテクチャを持つオブジェクト指向システムにおいて、一般的なレイヤ構造におけるドメイン層を記述することに重点を置いている。平たく言うとDDDとは、ユビキタス言語と呼ばれる共通の語彙をドメインエキスパートと開発者との間に確立することで仕様の決定の迅速化やその変更に対する堅牢性を維持し続けようとする方法論になります。これはいくつかの具体的なデザインパターンやドメインモデルの実装方法を提供しますが、その中に「サービス」という考え方があり、それをRailsでどのように実現したかについてご紹介します。サービスとはドメインの知識のうち、どのドメインモデルエンティティ*2にも属さない振る舞いあるいは複数のドメインモデルエンティティを操作するような振る舞いをDDDでは「サービス」と呼びます。ドメインエキスパート等の仕様策定者が名前をつけて呼ぶ単語の中で動詞としてよく登場するものはサービスである可能性が高いです。会員登録)以下のような特徴を持つ機能はサービスとして定義することを考えてみてください。モデルAR継承クラスのクラスメソッドとして定義されている/しようとしているメソッドの引数として別のモデルAR継承クラスのインスタンスを必要とする物理層(永続化層)における複数テーブルに対してINSERT/UPDATEを行う(トランザクション制御が必要)DB登録、API通信、メール送信など外部システムや外部リソースとの連携を同時に行うただし、永続化層や外部リソースとの連携はそれがサービスかどうかの判断のためには気にしますが、それ自体はサービスの責務ではありませんので注意してください。APIであろうとそれは適切に抽象化されるべきであり、サービスはその抽象化した結果のオブジェクトに処理を委譲するべきです。実装は以下のようにしています。endendこれは会員登録サービスの概形になります。会員登録と同時にウェルカムメッセージを送るという仕様を想定していて、後者をサブサービスとして別サービスにしていることがおわかりいただけると思います。このようにサブサービスとして別クラスにすることには以下のようなメリットがあります。上述したサービスの実装を見ていただければわかる通り、これらのサービスクラスはコンストラクタで必要な情報をすべて渡し、実行する際に引数を必要としません。これは仕様変更によってサービスの実行に必要な情報が増減した場合*3に、このサービスクラスを利用する側への影響を極力小さくしたいという意図があります。do!メソッドがパラメータを受け取る場合はこれを呼んでいる全ての箇所を修正する必要がありますが、コンストラクタで必要な情報をすべて渡していれば影響範囲をオブジェクト生成箇所に局所化できます。僕は最近は「フォーム」という層を作り、そこでサービス生成をさせています。フォームとはフォームはソフトウェアを利用しているユーザからの入力パラメータを受け取り、その検証/加工を責務とするクラスです。これ自体はドメインの知識ではなく、アプリケーションの関心事なのでDDDが提供しているデザインパターンというわけではありませんが、先に述べた通り、サービスを生成する層として非常に適しているのでご紹介します。Rails4ではActiveModel::ModelというActiveRecordから永続化の機能を抜いたモジュールを利用できるようになったため、そちらをincludeしてフォームとして利用できます。ActiveModel::Modelは永続化の機能以外はActiveRecordと同等ですのでform_forによってレンダリングすることが可能になります。endendこのようにフォームを導入することによりコントローラは以下のように非常にすっきりします。endendサービスのコンストラクタがもっと複雑になってしまった場合にはフォームが複数(入力パラメータの検証とサービスの生成)の責務を負うことになり、単一責務の原則に反してしまいますので、その場合は別途Factoryパターンを適用する等の工夫は必要だと思いますが、サービスの生成が極端に複雑でない場合はこれで十分でしょう。ご意見募集DDDの考え方をRailsに導入するということにはDDDを学び始めたばかりの頃から意識はしていたのですが、ようやく最近実プロジェクトで導入するようになってきました。これらはDDDで紹介されてる考え方やデザインパターンを参考にはしていますが、実装そのものは僕がほぼ我流で見いだしたところもあり、もっと効率的なやり方や不適切な箇所等があればご指摘/ご意見いただきたいと思います。