レイヤー設計とか、オブジェクト指向とか、DDDとか、その辺

自分の指向としては、技術の勉強というとDDDとかOODとか、そういう抽象的方面が好きなのだが、オブジェクト指向否定論もあることは承知している。

 

---------------追記 2020/09/27

この記事は「ユーザーのメンタルモデルを反映させる」というMVCの本来の設計思想を捉えていません。ただ、巷に流布しているものを元に考えた記事としては資料的には無価値ではないと思いますので、ログとして残しときます。

 

MVCの本来の考えはOOUIの本や、コプリエンのLean Architecture本が良さそう

-------------------------------

 

過剰設計の落とし穴

実際、レイヤー構造や業務モデルを頑張って作っているが、実装時に足かせになったり、プログラマがよく規約や方針を理解できずに、ごちゃごちゃに作り、余計に複雑になってしまったりするのは、色々聞く。(自分はこれをやりがち)

また、手続き型でやっていても、所謂「共通処理」にしたが、仕様変更や性能問題が発生し、共通処理が恐ろしく複雑になったりすることもある。そういう時は、コピペの方がまし、という判断もあり得る。

 

とは言え、フレームワークを使っている時点でモジュール化やオブジェクト指向の恩恵を受けているわけなので、そういうビジョンを全く否定するのはどうかと思う。開発体制やプロセスの現実を無視して理想を追求しても、問題しか起こさないっていうのは当然として。

 

改めてレイヤードアーキテクチャについて

最近考えているアプリケーションのレイヤードアーキテクチャについて思うところがあるので書く。一つの観点はドメイン知識はどこに表現されるのか。

 

よく新人研修とかでWebアプリケーションは、MVCで作るべき、と教わる。技術的には

シンプルなMVC

M モデル:POJO

V ビュー:UI担当。Web技術に依存。JSP

C コントローラ:サーブレット、もしくはフレームワークが提供するコントローラ。Web技術に依存。

で、ここにデータベースアクセスが加わるとDAOパターンを適用する。

DAOパターンはデータソースにアクセスするDAOと取得したデータであるDTOのセット。Daoパターンでは本来はデータベースに依存しないでデータソースからオブジェクトを取得するもののはずだが、テーブルを意識したインターフェースしか提供していない場合が多い。当然、DTOもテーブル構造に依存する。本来のDAOパターンに翻訳するとDTOはデータソースに依存せざるを得ないだろう。

ここで、DTOはモデルなのか、という話がでてくる。

トランザクションスクリプトなら、ビジネスロジックは手続き的なスクリプトで記述されるため、モデルの構造はそれほど気にしない。だから、DTOをそのままコントローラであつかっても問題ない。

この場合、

MVCWithDAO

M モデル:DTOとDAO。データソースに依存。データ取得ロジックにドメイン知識が表現される。

V ビュー:UI担当。Web技術に依存。JSPとか

C コントローラ:サーブレット、もしくはフレームワークが提供するコントローラ。Web技術に依存。DTOを処理するスクリプトドメイン知識が表現される。

 

上記の場合、コントローラーの責務が大きすぎるので、少し改良する。

 

MVCSwithDAO

M モデル:DTOとDAO。データソースに依存。データ取得ロジックにドメイン知識が表現される。

V ビュー:UI担当。Web技術に依存。JSPとか

C コントローラ:サーブレット、もしくはフレームワークが提供するコントローラ。Web技術に依存。

C’ サービス:コントローラからユーザーからのインタラクション(httpリクエスト)毎に呼び出される。DTOを処理するスクリプトドメイン知識が表現される。

 

上記のパターンは、PoEEAのサービスレイヤにかなり近いのではないかと思う。ここまで来ると、ある程度、レイヤーに割り振ることができる。

 

MVCSwithDAO(レイヤー追記)

M モデル:DTOとDAO。データソースに依存。インフラ層(永続化層)。データ取得インタフェースにドメイン知識が表現される。

V ビュー:UI担当。Web技術に依存。JSPとか。UI層

C コントローラ:サーブレット、もしくはフレームワークが提供するコントローラ。Web技術に依存。アプリケーション層。

C’ サービス:コントローラからユーザーからのインタラクション(httpリクエスト)毎に呼び出される。ドメイン層。DTOを処理するスクリプトドメイン知識が表現される。

 

上記のドメイン層のサービスは複数処理に分割できる。データベースをキッチリ固めれば、個々のプログラマが考える事が限定される。ドメイン知識はサービスに集約されているので、デバッグ時の可読性もそれなり。ただ、複雑な業務では、サービスが異常に複雑化するし、共通部分を抽出することが非常に難しい。

大抵のSI屋のプロジェクトではこうやっているんじゃないかな。

ただ、これはファウラーのいう「ドメインモデル貧血症」。

 

MVCSwithDAOにちょっと、工夫をするとかなり改善できると思う。DTOのカラムにValueObjectを利用し、DTOに振舞いを持たせる。これはMyBatisやDoma2などのORMフレームワークとの相性もいい。残念ながら、JPAは微妙。

 

MVCSwithDAO2

M モデル:RichDTOとDAO。データソースに依存。インフラ層(永続化層)。データ取得インタフェース,DTOが持つサービス、バリューオブジェクトにドメイン知識が表現される。

V ビュー:UI担当。Web技術に依存。JSPとか。UI層

C コントローラ:サーブレット、もしくはフレームワークが提供するコントローラ。Web技術に依存。アプリケーション層。

C’ サービス:コントローラからユーザーからのインタラクション(httpリクエスト)毎に呼び出される。ドメイン層。DTOを処理するスクリプトドメイン知識が表現される。

 

pureなドメインモデルから見て足りないところは、モデルの構造がデータソースの構造に制約されてしまうこと。また、ファーストクラスコレクションとか、イミュータブルなEntityとかそういうのは使いにくい。ただ、構造がシンプルであるため、たくさんの開発者が一斉に開発する場合には、かなり有効と思う。変な委譲の連鎖で処理が追いにくくなるリスクも少ない。プロジェクト進行中に、アーキテクチャのガバナンスが弱まっても最低限の品質は保たれる可能性が高い。

データベースの変更には弱いので、DOAで開発すべきと思う。

 

ActiveRecord

自分の中では、ActiveRecordは上記のパターンMVCSwithDAO2とかなり近いと思う。ただ、ActiveRecordはテーブル構造がモデルにそのまま反映されてしまうのに対して、DAOを1枚使うことで、柔軟になる。ちなみに、自分はJPAActiveRecordは使う側にとって、殆ど違いはないと思っている。Spring のCrudRepositoryは自分の中ではDAO。JPAのEntityはDTO

非正規化をやっていたり、過去の遺産のカラムが色々ある場合はJPAActiveRecordを利用するのは、やめた方がいいと思う。

 

じゃあ、ここにPureなドメインモデルを導入するとどうなるのか。結構整理が難しい。ちなみに、Pureなドメインモデルのメリットはモデルがテーブル構造などのデータソースが持つ構造や複雑性から自由になること。

 

ドメインモデル

V ビュー:UI担当。Web技術に依存。JSPとか。UI層

C コントローラ:サーブレット、もしくはフレームワークが提供するコントローラ。Web技術に依存。アプリケーション層。

M ドメインモデル:データソースにも、アプリケーション層にも依存しない。純粋なドメイン知識の表現。

I インフラ:データソースへのアクセス等を提供する。

 

ドメインモデルとしてのRepository

DDD本にあるRepositoryはEntityのCRUDなどを提供する。その意味で、DAOに近いし、実装内容は殆ど同じ。多分、DAOパターンが書かれたころは、基本はデータソースに依存しないEntityを提供する役割を持たせるもので、同じ概念だったのだろうと思っている。

自分の中での整理は、DAOが扱うオブジェクトはデータソースに依存し、Repositoryが扱うオブジェクトはデータソースに依存しない純粋なPOJO

gist35d6d4bfed3fe9d1615f

 

純粋なドメイン層と言えるのは、Repositoryのインタフェースまでで、実装はデータソースに依存せざるを得ない。

 

アプリケーション層でRepositoryは呼べるのか?

Java界隈では、アーキテクチャの一つの定石として、コントローラからはRepositoryを呼んではいけない、っていうのがある。(これは海外でもあるっぽい。)代わりにServiceを呼ぶべきであると。アーキテクチャ設計でのServiceって言葉はドメインって言葉と同じく色んな使われ方をしているので、解釈は難しいが、自分の意見は下記。

・ここでいうサービスがPoEEAにおけるServiceLayerなら、同意。そもそも、ドメインオブジェクトのインタラクションはServiceLayerに委譲するので、Repositoryどころか、他のEntityの振舞いとかドメイン層の振舞いはどれもやってはいけないはず。コントローラの役割はServiceLayerの結果をビューに渡すなどの処理のみしか許されていない。(と自分は思っている。)

・ServiceがDDDにあるような1ドメインモデルとしてのServieならRepository禁止はDAOと混同しているからで、純粋なドメインモデルとしてのRepositoryはコントローラで呼んでもいいはず。Serviceをかぶせることを強制してしまうと、それこそ、どこで何をやっているのか分からなくなる。

一般的なEntity問い合わせの責務はRepositoryに集約されるべき。業務ルール上、フィルタ等が必要なら、Repositoryのインタフェースでそれを表現するか、Repositoryで取得した後、適切なAggregateなり、Serviceなりがフィルタするインタラクションをコントローラで実装すればいい。

ドメインモデルのインタラクションがコントローラで書かれようと、PoEEAにおけるServiceLayerで書かれようと、ビジネスロジックの重要部分が1モジュールのコードに表現されていなければいけないと思う。

 

フレームワークが前提としているのは???

Seaser2にしろ、Springにしろ、基本的にフレームワークが前提としているのは、自分の整理におけるMVCSwithDAO2だと思う。個人的にもこれが現実解だと思う。Pureなどメインモデルを作ってやり切るのは、それぞれの機能でコア要員に技術スキルと業務分析スキルが両方必要なため、ハードルは高い