はじめに
「Controller にビジネスロジックを書くな」と言われることがしばしばあると思います。
この記事では、そもそもそれは何がいけないのか、どうすればいいのかを整理しました。
具体的なコードまでは書いていないですが、各ケースを図で表現して、できるだけ分かりやすいようにまとめました。
「Controller に全部書く」とはどんな状態か
「Controller にビジネスロジックを書くな」の対応の前に、「Controller に全部書く」という状態について整理しようと思います。
この記事で言う**「Controller に全部書く」状態とは、MVC を使っていて、Model クラスがデータの入れ物 (多くの場合、DB スキーマとほぼ一致) で、Service クラスが存在せず、プログラムの処理が全て Controller に書かれている状態**です。
イメージとしては、以下の図のような状態です。
この状態だと
- Controller が肥大化し、コードの見通しが悪くなる
- Controller をまたがって共通化すべきロジックをうまく共通化しにくい
- 自動テストしにくい
などの問題が発生します。
主に Ruby on Rails などのレイヤー分けを前提としない MVC フレームワークや、Express などの非常に自由に書ける軽量フレームワークでこの状態になることが多いと思います。
「Controller にビジネスロジックを書くな」は本当にそうなのか
「Controller にビジネスロジックを書くな」と言われることは多々ありますが、個人的に**「Controller に全部書く」を全否定するつもりはありません**。
状況によっては全然やってもいいと思います。
例えば、ほぼビジネスロジックがないうえ、自動テストもそれほど重視しないようなアプリケーションであれば、Controller に全部書いてしまえば十分だと思います。
(そもそもそんなアプリケーションを作る必要があるのか、ノーコードで作ることができないかは要検討だと思います)
また、「Controller にビジネスロジックを書く」ことの一番のメリットは、学習コストの低さだと思います。
学習コストが低いということは、チーム内の認識合わせのコストも低く、開発メンバーも集めやすいです。
ビジネス上それが優位に働くという判断であれば、開発の初速を高めるために Controller に全部書く方針もダメではないと思います。
ここは反対意見を持つ方もいらっしゃると思うので、これ以上は書かないことにします。
「Controller にビジネスロジックを書くな」の対応パターン
さて、話を戻して、ここから「Controller にビジネスロジックを書くな」の対応パターンを書いていきます。
この記事で紹介する対応パターンは大きく 3 つに分かれます。
-
- Service にビジネスロジックを書く
-
- Model にビジネスロジックを書く
-
- Service と Model にビジネスロジックを書く
それぞれどんな方法で、メリット・デメリットは何なのか、順に説明していきます。
※ 「2. Model にビジネスロジックを書く」については、さらに 2 パターンに分けて解説します。
1. Service にビジネスロジックを全部書く
Controller に書いていたプレゼンテーション以外の処理を Service に移動します。
この変更は分かりやすいので、比較的簡単です。実施すると下図のようになります。
Spring Framework のような Service クラスの導入を前提としたフレームワークの場合、特に考えなくてもこのような状態になっていることが多いでしょう。
さらに言えば、「DB とのやりとり」も Repository などの別クラスに配置されていることが多いと思います。
この状態は先ほどに比べて、Controller がとても軽くなっています。
自動テストのしやすさも、Controller に全部書くよりは向上したと思います。
しかし
- Service が肥大化し、コードの見通しが悪くなる
- Service をまたがって共通化すべきロジックをうまく共通化しにくい
- Service の自動テストがまだまだ大変
といった、Controller で発生していたのと同じような問題が Service で発生することになります。
つまり、この状態は Controller にビジネスロジックを全部書いているのとそれほど大きくは変わらないのです。
2. Model にビジネスロジックを書く
今度は Service クラスの導入をやめて、Model にビジネスロジックを書くパターンです。
2-1. Model にビジネスロジックを全部書く
まずは Model にプレゼンテーション以外の全てを担当させることを考えてみます。
上図を見て分かる通り、これは Service にたくさんの役割を任せたパターンと同じような問題が発生します。
さて、何がいけなかったのでしょうか?
以前「「ビジネスロジック」とは何か、どう実装するのか」という記事で書きましたが、
ビジネスロジックは 2 種類あります。
- 「コアなルール」である「エンタープライズビジネスルール」
- 「処理の流れ」である「アプリケーションビジネスルール」
の 2 つです。
この 2 つのうち、Model に入れるべきはエンタープライズビジネスルールの方です。
アプリケーションビジネスルールまで Model に入れてしまうと、それは結局 Controller に全部書いたり、Service に多くの役割を持たせるのと同じことで、その大変な役割を Model に持たせただけになります。
2-2. エンタープライズビジネスルールだけを Model に書く
それでは、「コアなルール」だけを Model に移動してみます。
「処理の流れ」は Controller に残しておきます。
これがどういうことかと言うと、「A をして B をして C をする」という処理の流れは Controller に記述し、「A をする」の処理の具体的な内容を Model のメソッドに記述するイメージです。
これによって
- Controller をまたがって共通化すべきロジックが Model に共通化できる
- Model に抽出したロジックが自動テストしやすくなる
という改善効果が得られます。
とはいえ、図を見てみると、Controller が大変そうです。
そこで次のパターンを見てみます。
3. Service と Model にビジネスロジックを分けて書く
Controller から**「処理の流れ」と「DB とのやりとり」を Service に移動**してみます。
このように Service クラスも導入すると、Controller とさらに役割分担でき、コードの見通しやテスタビリティがより向上します。
結局どうするのがオススメか
さて、「Controller にビジネスロジックを書くな」の対応パターンを解説してきたので、最後に結局どうすればいいのかをまとめます。
- レイヤー分けを前提としない Rails のようなフレームワークの場合
- Service を使う前提の Spring のようなフレームワークの場合
の 2 つに分けて書いていきます。
レイヤー分けを前提としない Rails のようなフレームワークの場合
Rails のようなフレームワークであれば、ここまでで書いてきたパターンのうち、
- Controller に全部書く
- 2-2. エンタープライズビジネスルールだけを Model に書く
- 3. Service と Model にビジネスロジックを分けて書く
のどれかがいいのではないかと思います。
この中でどれを採用すべきかは状況によって変わると思います。
上ほど初期実装・学習コストで有利、下ほどメンテナンス性・テスタビリティで有利です。
オススメとして書かなかった
-
- Service にビジネスロジックを全部書く
- 2-1. Model にビジネスロジックを全部書く
については、Controller の代わりに Service か Model が肥大化することになるので、あまり根本解決にならないのではないかと思います。
結局 1 箇所に色々な役割を持たせてしまうと何も解決しないわけです。
**本来やりたいのは「Controller を軽量にすること」ではなく、いわゆる「関心事の分離」というもので、平たく言えば「クラスごとの適切な役割分担」**です。
Service を使う前提の Spring のようなフレームワークの場合
Spring のようなフレームワークの場合、
- 1. Service にビジネスロジックを全部書く
- 3. Service と Model にビジネスロジックを分けて書く
のどちらかになります。
これはいわゆる**「トランザクションスクリプト」と「ドメインモデル」のどちらを採用するかという議論と同じ話**です。
より処理を分割していくには
ここまでで、Controller に全部書かれた状態から処理を抽出していく第一歩を解説しました。
ここからさらに処理を分離していくための、様々な対応策が考えられます。
例えば
- 「表示用の変換」を抽出する
- 「DB とのやりとり」を抽出する
- リクエストと対応するクラスを Model とは別に作る
- View 専用のデータの入れ物クラスを Model とは別に作る
といった方法が考えられます。
特に**「DB とのやりとり」については、下図のようにデータアクセス層としてしっかり分離するケースも多い**と思います。
注意
さて、ここまで役割分担を進めると色々メリットがあるという話をしてきましたが、気軽にやると逆効果になる場合もあります。
1. 初期実装コストとのトレードオフがある
この記事で紹介してきた分割は、基本的に初期実装コストとトレードオフになります。
「Controller に全部書く」パターンであっても、1 つのメソッドが何十行、何百行とかでなく、適切に private メソッドに抽出されていれば十分読める場合もあります。
1 つのメソッドが何十行、何百行もあったりする場合は、そもそも「リーダブルコード」的なものが取り入れられていない状況です。
その場合は分割という高度な着手する前に、手続きを関数に切り出すことから取り組み始めた方がいいかもしれません。
2. 分割しすぎるとフレームワークと相性が悪くなる場合がある
このような処理の分割は、あまり分割しない代わりに高速に開発できる Ruby on Rails のようなフレームワークのメリットを潰す可能性があります。
こういった手法を採用する際は、手法とあったフレームワークを使うとよりメリットを感じられると思います。
まずは使っているフレームワークでの定番パターンで実装していって、言語・フレームワークの特徴を十分理解し、その上で少しずつ分割していくような流れでもいいのかもしれません。
参考
Web
書籍
- エリック・エヴァンスのドメイン駆動設計
- 実践ドメイン駆動設計
- .NETのエンタープライズアプリケーションアーキテクチャ 第2版
- 「実践ドメイン駆動設計」から学ぶDDDの実装入門
- 現場で役立つシステム設計の原則 ~変更を楽で安全にするオブジェクト指向の実践技法
- ドメイン駆動設計入門 ボトムアップでわかる! ドメイン駆動設計の基本
- Clean Architecture
関連記事
以下、自分が書いた関連記事です。