アプリケーションの設計で「サービスクラス」というものがしばしば使われますが、その「サービスクラス」が何を指しているのか、状況によって違ったりしないでしょうか?
この記事では、そんな「サービスクラス」についての私の理解を、3 種類に分けて整理していきます。
その中で、「サービスクラス」がアンチパターンと言われる理由なども書いていきます。
結論 - 3 種類の「サービスクラス」
最初に結論として、3 種類のサービスクラスについて書いておきます。
- 「トランザクションスクリプトパターン」における「サービス」
- 「ドメインモデルパターン」における「アプリケーションサービス (= ユースケース)」
- 「ドメインモデルパターン」における「ドメインサービス」
の 3 つです。
ここから、この 3 パターンを整理するための前提からまとめていきます。
前提 - 「ビジネスロジック」について
アプリケーションを 3 層に分けて考える
まず、アプリケーションの役割を 3 層に分けて考えてみます。
「プレゼンテーション」・「ビジネスロジック」・「データアクセス」の 3 つです。
典型的には、いわゆる「3 層アーキテクチャ」として、例えば以下のようなクラス構成をとったりします。
ただし、ここで言う 3 層に分けて考えるというのは、そうクラス分けするという意味ではないです。
この 3 種類の役割をまたがったクラスが登場するケースもあります。
例えば、Ruby on Rails などでよくあるコードでは、以下のような構成になっていたりします。
ビジネスロジック層の役割
「サービスクラス」に関する議論と関係するのは、上記の 3 層の中の「ビジネスロジック層」の部分です。
ビジネスロジック層の役割には、大きく以下の 2 つがあります。
- ユースケースの実現
- コアなルール
「ユースケースの実現」というのは、例えば「じゃんけんして、その内容を保存して、結果を返す」といった処理の流れです。
「コアなルール」というのは、じゃんけんで言えば「グーがチョキに勝ち、チョキがパーに勝ち、パーがグーに勝つ」といったルールのことです。
「コアなルール」は「ドメインロジック」と言われたり、狭い意味でこれを「ビジネスロジック」と呼ぶ場合もあります。
ビジネスロジックという単語については、こちら の記事にもう少し書かせていただいています。
ビジネスロジックの実装パターン
ビジネスロジックの実装方法には、大きく
- トランザクションスクリプトパターン
- ドメインモデルパターン
の 2 パターンがあります。
それぞれ以下の図のような役割分担になります。
※ ドメインモデルパターンでは、ビジネスロジック層を「アプリケーション層」と「ドメイン層」に分割したりするので、この図でもそのように表現しています。
このどちらのパターンを使っているかによって、「サービスクラス」の意味が変わります。
本題 - 3 種類の「サービスクラス」の整理
それでは、本題の、3 種類の「サービスクラス」について書いていきます。
1. 「トランザクションスクリプトパターン」における「サービス」
トランザクションスクリプトパターンにおける「サービス」は、「ユースケースの実現」と「コアなルール」を両方担うクラスです。
よくある 3 層アーキテクチャで見られるもので、単純に「サービスクラス」と言った場合、このパターンを指していることが多いと思います。
Spring Framework などではこの「サービス」を設けるのが一般的だと思います。 一方で、Ruby on Rails などでは、典型構成としてはこの「サービス」は使わないことが多く、その役割も Controller に持たせてしまう実装をよく見ます。
このパターンの欠点は、「サービス」が「ユースケースの実現」と「コアなルール」の両方を持ち、役割が多すぎることです。 そのため、この「サービス」はアンチパターンと言われることがあります。 ただ、それは「サービス」が悪いというよりも、「トランザクションスクリプトパターン」に起因する問題です。
「トランザクションスクリプトパターン」には、学習コストが低いというメリットがありますが、デメリットとして Service や Controller が “Fat” になりやすいです。
Fat Controller の解消法については、こちら の記事に書かせていただいています。
2. 「ドメインモデルパターン」における「アプリケーションサービス」
次に、「ドメインモデルパターン」の場合についてです。
「ドメインモデルパターン」においては、「アプリケーションサービス」と「ドメインサービス」の 2 種類が登場します。
「アプリケーションサービス」は、「ユースケースの実現」のみを担うクラスで、「コアなルール」は持ちません。
「アプリケーションサービス」は、別名「ユースケース」と呼ばれます。
XxxApplicationService といったクラス名よりも XxxUseCase といったクラス名の方が役割がわかりやすいので、UseCase という命名が望ましいかもしれません。
この「アプリケーションサービス (= ユースケース)」については、特にアンチパターンとは言われていないと思います。
ただ、Rails などを使う場合は、この「アプリケーションサービス」の役割は Controller に持たせてしまうのも一つの手だと思います。
3. 「ドメインモデルパターン」における「ドメインサービス」
ドメインモデルパターンにおいては、「コアなルール」はできるだけデータを持つクラスのメソッドとして実装します。
しかし、どうしてもどのクラスに持たせてもしっくりこない処理が発生するケースがあります。
そういった状況で「コアなルール」の一部を実装するクラスが「ドメインサービス」です。
このクラスには「サービス」という名前がついておらず、そのクラスの役割に応じた適切な名前がつけられる場合もあります。
この「ドメインサービス」については、使いすぎはアンチパターンであり、できるだけ、データを持つ「モデル」に処理も持たせてあげるべきと言われます。
なお、トランザクションスクリプトパターンであっても、「サービス」や「コントローラ」間で共通の処理を切り出す先のクラスとして「ドメインサービス」のようなクラスが利用されるケースもあります。
おわりに
以上、「サービスクラス」について私の理解を整理してみました。
この記事では色々な用語を使って整理してきましたが、用語の定義は人によって違うことが少なくありません。 その点はご注意ください。
認識合わせのために用語の定義を合わせるのも大事ですが、設計という観点で重要なのは「そのクラスの役割は何なのか」だと思います。
また、個人的に、「サービスクラス」は特にアンチパターンとは考えていないです。 ポイントは役割の多すぎるクラスを作らないことで、その解消のために「サービスクラス」が役立つのであれば、取り入れるべきだと思います。
最後になりますが、こちらの記事の内容は、私が見たことのある範囲のまとめになります。 ご指摘などあれば、Twitter などで教えていただけると嬉しいです。
参考書籍
- エンタープライズアプリケーションアーキテクチャパターン
- エリック・エヴァンスのドメイン駆動設計
- 実践ドメイン駆動設計
- .NETのエンタープライズアプリケーションアーキテクチャ 第2版
- Clean Architecture
- ドメイン駆動設計入門 ボトムアップでわかる!ドメイン駆動設計の基本
- 「実践ドメイン駆動設計」から学ぶDDDの実装入門
関連記事
以下、自分が過去に書いた関連記事です。