ビジネスロジックの実装の2つのパターン

大嶋勇樹氏:ここまでの流れは、「そもそも3層アーキテクチャって何だっけ?」というところから、特に「真ん中のビジネスロジックって何だっけ?」と(いう話)、「例えば、このあたりがビジネスロジックだよね」と(いう話)。(そして)「ビジネスロジックの中には、ドメインロジックとユースケースの2種類があると考えるとわかりやすいですよ」というところまで話してきました。

ドメインロジックは、システム都合ではないコアなルールみたいなもので、ユースケースは処理の流れを実現することです。これを踏まえて次は、ビジネスロジックの実装方式を見ていこうと思います。

と思ったんですが、ちょうど(発表を始めてから)50分(経った)ぐらいのタイミングなので、ここで5分休憩しようと思います。その間に質問があれば、Q&Aに書いてもらえれば回答します。

(5分経過)

5分経ったので、続きをやっていければと思います。

(スライドを示して)また「リバーシ」で石を打つ時の処理の例を見てみると、2つ目あたり(から)がビジネスロジックです。ここをすごく単純化して言うと、データの取得やデータの取得の呼び出し、データアクセス層のデータ取得のメソッドの呼び出しがあって、チェックやひっくり返す計算処理があって、最後にデータを保存する処理の呼び出しがあります。

これを踏まえて見ていこうと思いますが、実はビジネスロジックの実装方式は、トランザクションスクリプトパターンとドメインモデルパターンという、2種類の実装パターンがあります。

(スライドを示して)2種類を並べて見ています。トランザクションスクリプトパターンは、Serviceクラスみたいなところに、データの取得の呼び出しやチェック、計算処理、データの保存の呼び出しみたいなことを全部実装します。

つまり、ビジネスロジックと言われていたものが全部入るんですね。ユースケースで、処理の流れや「ここはひっくり返せるか」みたいな具体的なチェック、「この石をひっくり返す」みたいな具体的な処理、ドメインロジックも書いてあります。

プラス、このServiceクラスはDTOやModelと言われたりする、データを入れるだけのクラスを使っていることが多いです。Modelと呼ぶかどうか、Modelという単語はいろいろな意味がありすぎて状況によるので、「Modelという言葉が使われることもありますよ」ぐらいの意味でここでは言っています。

こういうDTOやModelというデータの入れ物を置いておいて、Serviceにメソッドというか、処理が全部書いてあるのがトランザクションスクリプトというやり方です。

(スライドを示して)一方、別のやり方としてドメインモデルパターンもあります。ドメインモデルパターンはなにが違うかというと、Serviceはユースケース、処理の流れを担うだけで、ドメインロジックをModelが持っています。ドメインロジックは、チェックや計算処理をデータの入れ物になるクラスに一緒に持たせるやり方ですね。

2つのパターンの特徴比較

(スライドを示して)ちょっと特徴を並べてみますが、トランザクションスクリプトパターンはデータの入れ物と処理を分離するやり方で、手続き型プログラミング的とよく言われます。

Serviceなり、なんとかLogicというクラスが、ドメインロジックとユースケースを担当します。この実装方式は、メリットとして学習コストが低いというか、プログラミングを勉強し始めて、別のやり方を教わらずに「コードを書け」と言われたら、この方式を書く人がほとんどだと思います。そういう意味で学習コストが低いです。

一方で、この実装方式をすると、Serviceクラスにいろいろなロジック、いろいろな分岐や処理が書かれるので、肥大化しやすいです。同じLogicがServiceクラス間に分散しやすく、変更に弱いという特徴もあります。

一方で、ドメインモデルパターンは、データの入れ物に処理も持たせる方式です。言ってしまえばオブジェクト指向プログラミング的ですね。オブジェクト指向プログラミングといっても指す意味が人によってけっこう違いますが、ここでは「データの入れ物に処理、メソッドを持たせるようなもの」をオブジェクト指向プログラミングと言っています。

そういうやり方で、Modelはドメインロジックを担当して、Serviceはユースケースを担当します。

こういう構成にすると、メリットとしてはServiceクラスが肥大化しにくく、同じロジックが分散しにくくて変更に強くなりやすいです。そしてトランザクションスクリプトの裏返しですが、デメリットとしては学習コストが高いこと。オブジェクト指向的なコードの書き方みたいなものが、ある程度わかっていないと書けないところですね。

質疑応答 Modelクラスを軸にした2つのモデルパターンの認識

今1個質問をもらっていて、すぐ回答できそうなので回答しようと思います。「ドメインモデルパターンはドメインモデルにModelクラスを定義して、Serviceは、Modelクラスからメソッドを呼び出すみたいなイメージですか?」ということですが、そうですね。

ドメインモデルパターンはModelクラス。Modelクラスというものはけっこう曖昧ですが、とにかく何かデータを持っているようなクラス。何かフィールド、データを持っているクラスを用意して、そこにメソッドも持たせて、サービスからそのメソッドを呼び出すイメージです。質問ありがとうございます。そういう構成ですね。

そうすると、Serviceはユースケースを担当するだけで、Modelはドメインロジックを担当する。そういう分担ができます。これはいわゆるオブジェクト指向的な設計みたいな知識が必要で、ちょっと難易度が高いです。

ドメインモデルを使用した構成例

(スライドを示して)具体的にどういう構成例になるかというと、ビジネスロジック層をアプリケーション層とドメイン層という2層に分割した例になります。こういう構成例が取れます。レイヤードアーキテクチャということもありますね。

これはプレゼンテーション層、アプリケーション層、ドメイン層、データアクセス層と、一方向に矢印があります。ここで矢印は呼び出しや依存の向きを表現していますが、一方向に流れているものをレイヤードアーキテクチャと呼んだりします。

どんな中身かというと、まずControllerは利用者から入力を受けつけたりして、Serviceを呼び出します。Serviceには、データを取り出してチェックしたり、計算して保存する処理の流れの呼び出しだけが書いてあります。

データを取り出すところは、データとデータベースでやりとりするようなクラスを使って、入れ物のクラスを受け取ったりするか何らか(の方法)で受け取って、ドメインモデルにロジックが書かれます。

この構成例だと、恐らくデータの入れ物を取り出したら、Serviceの中でドメインモデルの型に詰め替えることになります。ドメインモデルのクラスにデータを詰め替えて、そこでメソッドを呼び出してロジックを実行します。

最終的に計算結果をデータベースに保存するイメージです。プラス、戻り値を返してUIを表示します。ざっくりこんなイメージです。

Repositoryパターンを使用した構成例

もうちょっと凝るというか、もうちょっと工夫すると(したら)、よくあるのはRepositoryパターンを使うやり方です。

(スライドを示して)データベースのテーブル都合で読み書きするTable Data Gatewayのような設計パターンを使うのではなくて、ドメインモデルの形式で最初からデータを読み書きするデータアクセス用のクラス、Repositoryみたいなものを使って、Serviceはドメインモデルを簡単に取り出せるようにします。

その中ではTable Data Gatewayみたいなものを使うのか、O/Rマッパーを使うのかわかりませんが、とにかくなんとかデータベースからデータを取り出して、ドメインモデルのかたちに変換して返すようなことをするRepositoryを使います。

ServiceはRepositoryからドメインモデルを取り出して、ドメインモデルを操作して、Repositoryに保存するようなやり方は、わりとよくある例だと思います。このような構成はレイヤー化アーキテクチャと言ったりしますね。

今、1個質問をもらっていて。「ドメインモデルパターンはドメイン駆動設計と同義ですか」ということですが、私はノーだと思っています。ドメイン駆動設計との関係については、最後に補足しようかなと思います。ありがとうございます。

(スライドを示して)こんなものがドメインモデルパターンを使ううちの、けっこう典型的な構成の一種みたいな感じです。

アプリケーション設計におけるさまざまな観点

(スライドを示して)ここまで「なんとかパターン」とか、「なんとかアーキテクチャ」とかいろいろ出てきましたが、今日出しているようなアプリケーションの設計の話の中には、いろいろな観点があります。

まず層分けをどうするかという話で、3層アーキテクチャ、レイヤードアーキテクチャ。ほかにもヘキサゴナルアーキテクチャ、オニオンアーキテクチャなどがあります。

層はだいたい3層なり、先ほど見せたようなビジネスロジックの部分をアプリケーション層、ドメイン層に分離するなりだったりします。そのうちプレゼンテーション層の実装方式もいろいろあって、MVC(Model、View、Controller)やMVVM(Mode、View、ViewModel)があります。

ビジネスロジック層の実装方式もいろいろあって、トランザクションスクリプトにするのか、ドメインモデルにするのか。データアクセス層はTable Data Gatewayにするのか、Repositoryにするのか。ほかにもいろいろあります。

こんなふうに、まず層分けがあって、そして各層のアーキテクチャ、設計パターンがあると考えるとちょっとわかりやすいかなと私は思っています。層分けと各層の実装方式という観点があって、組み合わせはいろいろあります。

その一例が、先ほどお見せしたレイヤードアーキテクチャ。プラス、プレゼンテーション層あたりはControllerとViewとMVCっぽくなっていますね。

(スライドを示して)Modelがどれかというと、アプリケーション層からデータアクセス層あたり全体がModelと言ってもいいのかもしれないし、Viewが直接見ているデータの入れ物をModelと言うのかもしれないし。そういう意味ではMVCのMはけっこう曖昧ですが、なんとなくMVCです。

プラス、ビジネスロジックの実装はドメインモデルパターンを使っていて、ドメインロジックはドメインモデルに持たせて、ユースケースはServiceクラスなりUseCaseなりの名前をつけることもありますが、そういうところに持たせます。

データアクセスする時はRepositoryを使っています。ただ、Repositoryの中ではTable Data Gatewayという別の設計パターンを使っています。こういうことがあったりするわけですね。

1個戻ると、例えばほかの組み合わせとしてよくあるのは、3層、MVC、トランザクションスクリプト、Table Data Gatewayとかです。前のほうで見た例ですね。

(スライドを示して)これは3層アーキテクチャに加えてMVC、さらにトランザクションスクリプト、さらにTable Data Gatewayみたいになっているわけです。

こんな感じで、層分けと各層に実装方式があると整理するとけっこうわかりやすいかなと私は思っています。

質問をもらっていますが、もう少し切りのいいところまで進めさせてもらいます。

アプリケーション設計にはいくつか異なる観点があります。1つは層をどうやって分けるかみたいな話です。3層、レイヤード、ヘキサゴナル、オニオン。そして各層をどうやって実装するか。MVC、MVVM、MVP、トランザクションスクリプト、ドメインモデル。Table Data Gateway、Repositoryなどいろいろありますが、各層をどう実装するか。そしてもう1つ。ドメインモデルをどうやって設計・実装するかという観点もあります。

この中でどれが重要かというと、全部重要だと思いますが、ドメインモデルをどう設計・実装するかがすごく大事ですね。最初はController、Serviceという役割分担に目が行きやすいのですが、それと同じかそれ以上に、ドメインモデルをどうやって設計するか、どうやって実装するかも大事です。

アプリケーション設計に関する書籍を読んだりする時は、この中のどの観点を今解説しているのか、また書籍全体でどこの観点を解説しているのかに注目すると、わりと理解しやすくなるかなと思っています。

ドメインモデルの設計・実装については、オブジェクト指向設計を解説した本の内容も役に立つので、そういうのも見てみるのもすごくいいかなと思います。

いろいろな言語であると思いますが、例えば『リファクタリング』もそうだし、Rubyの『オブジェクト指向設計実践ガイド』もそうだし、そういうのは「ドメインモデルをどう実装するか」にけっこう活かしやすい気はしています。

ほかのところにも活かせる点はありますが、特にドメインモデルの設計に活かしやすい気はしています。

質疑応答 Repositoryパターンを採用する基準は?

1個質問をもらったので回答しようと思います。「Repositoryパターンを採用する基準などはありますか? ドメインモデルから直接呼び出したくない都合があるとかですか?」ということです。

Repositoryパターンはちょっと説明が難しいので、この場では曖昧にしか説明しなかったのですが、実はちょっと発展的な設計パターンです。

まず前提として、データベースと1対1対応するようなデータの読み書きクラスを作る時、それに(対して)Repositoryと名前をつけることがたまにあるのですが、それはRepositoryパターンではありません。

Repositoryパターンは、データベースのレコードと対応する読み書きクラスを作るわけではなくて。ドメインモデルはデータの入れ物兼ドメインロジックみたいな感じで、ドメインモデルに都合のいい単位で読み書きするクラスです。

もしRepositoryがないと、Serviceクラスがまずデータベースのレコードに対するクラスを取り出して、それをドメインモデルに詰め替えて、ドメインモデルの処理を呼び出して、データベースのレコードに対応するクラスに詰め替えて保存するみたいな流れになります。

その詰め替えの部分をRepositoryが担ってくれるイメージが、個人的には最初はけっこうしっくりきやすいかなと思っています。データベースのデータの入れ物なりなんなりから、ドメインモデルに詰め替えて扱います。保存する時もドメインモデルで渡せば、いい感じにデータベースに保存してくれるのがRepositoryです。

ここは実際のコードがあったほうがよりイメージはつきやすいと思います。Repositoryパターンは実は意外と難しいですね。質問ありがとうございます。

(次回に続く)