前置き
クリーンアーキテクチャなどの一般的なアーキテクチャを適用するにあたり悩ませる問題がある。
“EntityをWebAPIに乗せても良いか?”
この問題は理想論とコストの兼ね合いがあり設計方針に委ねられる事となるが私は大反対である。
何故なら不必要なデータが含まれていたり構造が限定的であるからであり、一言でいうと”UserをWebAPIで取得したらPasswordも付いてきた”という事態になりえるからだ。
それ以前に私はEntityが何処からでも参照できるようにしてはいけないと考えている。DDDでもUI層からEntityにアクセスするのはマナー違反とされています。しかし一般的なアーキテクチャの図説を見る限りDomain層は中心にあり参照しても良いように見えてしまう。
一方、値オブジェクトとなると逆に利用されるべきであると考える。WebAPIに乗せても良いし、むしろシリアル化のために別クラスを定義するのは非常に無駄だ。
つまりはEntityと値オブジェクトはスコープが大きく異なる。これは実現するに辺り一般的なアーキテクチャの図説のような円だけでは表現できない設計になる。しかしコードが増える訳でもないのでコストは増えないはず。
Application層を全てMediatorパターンで構築した際はより良い完結したアーキテクチャとなりうると考える。
概要
Domain層にDomain.Common
、Application層にApplication.Common
というモジュールを作り、そちらに値オブジェクト系を定義するようにした。たったそれだけだがEntityとアプリケーションサービスがサーバーサイドにのみ存在するようになる利点がある。もしサーバーサイドのみのWebページならWebAPI系のモジュールを消せばいいだけである。その際はUIはサーバー側になるがEntityに依存しない。当然だがMediatorパターンを適用しているためApplicationモジュールは何処にも依存しない。
Domain.Commonモジュール
シリアライズ可能な不変オブジェクト、つまり値オブジェクトを定義する。モジュール名はCommonでなくValueObjectsでもいいと思う。私は値オブジェクトはJSONやMessagePackに依存させてしまった方結果的に楽だと考えている。それが嫌なら下記のWebAPIモジュールにシリアライザを定義しましょう。名前空間はCommonを端折るとコードが綺麗になる。
Domainモジュール
EntityやDomainServiceを定義する。IUnitOfWorkのようなインターフェイスもここ。
Application.Commonモジュール
Mediatorパターンの引数(Request)と戻り値(Response)を定義する。モジュール名はCommonでなくValueObjectsにしたかったがインターフェイスが入り込む可能性があるためCommonを採用した。RequestとResponseは可能な限りシリアライズ可能にすべき。非同期通信など複雑な機能を実装する際はインターフェイスを戻り値にしたいケースもあり、そのインターフェイスもここで定義する。名前空間はCommonを端折るとコードが綺麗になる。
Applicationモジュール
サーバー側のApplication層。Mediatorのハンドラを定義する。
WebAPIモジュール
定数やシリアライザを定義する。私は値オブジェクト等でシリアライズ系の属性を付けてシリアライザは書かない派。SignalRを使うならHubのインターフェイスをここで定義するとリファクタリングしやすくなる。
WebAPI.Serverモジュール
各URLに紐づくコントローラを定義する。
WebAPI.Clientモジュール
クライアント側のApplication層のような物。Mediatorのハンドラを定義する。基本的にWebAPIを呼ぶ処理だけ。
コメント