前後編に分けています.
後編:ドメイン駆動設計+クリーンアーキテクチャ解説【クリーンアーキテクチャ編】
#1.はじめに
今回はドメイン駆動設計+クリーンアーキテクチャについて記載したいと思います。
実は僕自身「エリック・エバンスのドメイン駆動設計」を読み、実際にプロジェクトに反映する際に、いくつか問題にぶち当たりました。
上記DDD本は非常に内容が濃く良書だと思いますが、反面抽象的な記述が多く、読み解く上で苦労した部分もあり、そういった所を踏まえ今回はできるだけ具体的な例を元に記載できればと思います。
また実践していく上でドメイン駆動設計だけでは、なかなか実装に落とし込めず、特に最初に導入したレイヤードアーキテクチャを実装していく上では、いくつか問題がありました。そこで上記解決策としてクリーンアーキテクチャを採用することになりまして、そういった経緯を含めて記載したいと思います。
内容については長くなりましたので、前篇のDDD編と後編のクリーンアーキテクチャ編に分けています。
勉強不足な点もあるかと思いますので、ご意見ありましたらお気軽にお願いします。
#2.背景
理想論を掲げた所で、現実のプロジェクト開発においては様々な問題が発生します。
下記の例のように、理想を実現するためには解決すべき現実的問題が存在します。
理想
- 外界(UI・外部API・ストア)からの独立
- コアロジックとそれ以外の処理の分離
- 技術的関心事からの分離
- 高い保守性
- 高い拡張性
立ちはだかる現実
- 外部API・ストアの変更に影響を受ける
(例)仮に三層モデルでストレージをRDSからDynamoDBに変更すれば、当然ロジックにも修正が入ってしまう。 - インピーダンスミスマッチ
- 外部ストアのデータ構造に依存してしまう。
(例)例えばIDがInt型であれば、ロジック層で扱う際 にIntとして扱う必要があり変更があった場合、ロジック層に影響が出てしまう。 - ドメインモデル貧血症
機能を拡張するにつれて、ロジックを扱う層がいつのまにかドメインモデルの上位に居座り、ドメインモデルの境界が侵食される。 - レイヤーの修正が、前後の層の実装に影響を与えてしまう。
#3. ドメイン駆動設計(DDD)
###3.1 ドメイン駆動設計とは
エリック・エバンス氏が提唱したソフトウェア開発手法で、1つの共通言語であるユビキタス言語を用いてドメインモデルを構築することで利用者の関心事を集約・隠蔽します。つまりドメインモデルとは利用者の関心事を要約したものです。
これによって、「実装の肥大」、「ストアに依存」、「開発者とビジネスサイドの人間の会話が一致しない」といった問題を防ぐことが可能になり拡張性をもたせることができます。
では利用者の関心事とは何でしょう?Facebookのメッセンジャーアプリをイメージしてみましょう。部屋を作成するという機能を作る場合、利用者の関心事はルームを作成する。その際に参加ユーザーを登録する。部屋名を決定するなどです。
では、それ以外の関心事はなんでしょう?
◯◯のindexが必要になる。idはInt型にしよう。RDBの場合、ルームの参加者を表現する1:nのリレーショナルテーブルを作成する必要がある、ルーム作成時にはトラッキング用のログを送信しよう等です。
これらを同時に語るべきではありません。別の問題として切り分ける必要があります。
そしてこのモデルは一度に出来るものではなく、仕様変更や機能拡張の度に何度も見直す必要があります。これはチーム全体の共通理解として進めていく必要があるため、繰り返し何度も研磨する必要があります。
- 例
###3.2 ドメイン駆動設計の構造
ドメイン駆動設計において、その定義とはドメインモデルが存在しまた隔離されていることです。他の領域と関わってはいけません。
下記図のようにWeb層やビジネスロジック層とは別にドメインモデルが存在しまたこの時、ドメインモデルをインフラストラクチャ層とマージしてはいけません。あくまで利用者の関心事をドメインモデルに落とし込む必要があります。
特にこの隔離するという部分は徹底的に行う必要があり、知らず知らずに他の処理にドメインに関する関心事が紛れ込んでしまうことがあります。
呼び出し側とドメインモデルの関係は契約の関係です。呼び出し側がドメインモデルの内部実装に関与してはいけません。これは契約の関係であり、呼び出す際には抽象インターフェースに対しての呼び出しのみ実施する必要があります。
###3.3 用語解説
ここでドメイン駆動設計における重要な用語について解説します。
####3.3.1 エンティティ
ドメインモデルにおけるデータ構造はエンティティと値オブジェクトを元に表現されます。
Mysql、DynamoDb等のテーブルを表現するオブジェクトをエンティティと表現する場合もありますが、今回のエンティティとは別物です。エンティティはデータストアの構成には依存しません。
(※曖昧さ回避のためテーブルを表現するオブジェクトを、この記事ではテーブルオブジェクトと表現します。 )
ここで表現されるエンティティとは、ライフサイクルを通じた連続性を持ち属性から独立されるものは全てエンティティとなります。すなわち同一性・連続性があるものをエンティティとして定義します。
エンティティにとって最も基本的な責務は連続性を確立することです。
例えばユーザ情報があった場合、ユーザの年齢や身長などの属性は変化するかもしれませんが、ユーザ自身であることは変わりません。
つまりユーザAはいつまでもユーザAであり、同一性・連続性を持っています。
####3.3.2 値オブジェクト
同一性を持たないものが値オブジェクトとなります。例えば同じ赤色のペンが2つあったとして、使用者にとっては同じペンとなります。
あるモデル要素においてその属性しか関心の対象とならないなら、それは値オブジェクトとして分類する必要があります。
また値オブジェクトには自分が伝える属性の意味を表現させ関係した機能を与え、値オブジェクトを不変なものとして扱う必要があります。
ただし注意点として上記のように属性を表現するものがイコールで値オブジェクトとなるわけではありません。
ここで住所について考察しましょう。メッセージアプリにおけるユーザにとって、住所は同一性はなく値オブジェクトとして扱うことができます。しかしユーザが配送先をWEB上で決め、それを元に集荷、配送を送るようなアプリケーションを考えてみましょう。この時送り先としての住所は管理され、同一性を持ちます。Aさんが送り先を指定し、その後変更した場合送り先としての住所には同一性があります。このようなケースではエンティティとして管理する必要があります。
####3.3.3 集約
これは区切られるスコープによって不当条件が維持されるべき範囲を示します。例えばFacebookのメッセンジャーのようなアプリケーションを例にした場合、「メッセージルーム」を作成時に必ず「ルーム参加ユーザ情報」が作成され、「メッセージルーム」データを削除したら「ルーム参加ユーザ情報」は削除されます。
これらのエンティティの集合は同一性があり、永続化に関して同一ライフサイクルを持ち、これを集約と表現します。
集約の対象はエンティティ及び値オブジェクトであり、これらのエンティティ・値オブジェクトの集合の中で、ライフサイクルの起点となるエンティティを集約ルートエンティといいます。
またドメイン駆動設計において、内部オブジェクトへのアクセスは必ず集約ルートを経由して制御する必要がある。
これは上記の永続化に関するライフサイクルを維持するためであり、ドメインモデルに関する操作についてはレポジトリを経由して行う。つまりレポジトリの数は集約ルートの数と同数である。
####3.3.4 サービス
ドメインの重要な操作でありながら、エンティティ・値オブジェクトに不適切なものをサービスという。これには幾つか条件があり操作がドメインの概念に関係しており、その概念がエンティティや値オブジェクトの自然な一部ではないもの、インターフェースが定義されている、操作に状態がない必要がある。
###3.4 ドメインモデルの構築
実際にドメインモデルの構築をする際には、ドメインモデル図を作成します。実際に図に書き起こし、機能要件と比較し反芻します。ここでDBの設計に引っ張られてはいけません。DBではInt型でもドメインモデル上ではIntである必要はありません。ドメインモデルで持つ型についてはInt,String,List<>などを使用せずそれぞれ独自の型を利用します。
ドメインモデルは、利用者の関心事なのでそれ以外を表現してはいけません。例えばDBで持ってる作成日時などのメタ情報や削除フラグなどです。同様にUIの事情に縛られる必要もありません。UIで必要なフォーマット、情報量はドメインモデルにおいては関係ありません。もし前述のような事情があったとしてもそれはWeb層で解決するものです。
例えばルームIDであれば、「RoomId」という型クラスを作成します。参加日時であれば「JoinAt」等の型を作成します。これは抽象的な型を、よりに人間の関心のある型に近づけるため、独立性の高い型を利用することで各オブジェクトをモジュール化し保守性を高めます。
独立性の高い型を利用することで保守性を高めるとはどういうことでしょう?実例を出してみます。
下記ドメインモデル上でルームエンティティのルームIDを独自型を使用せずInt型で持っていたとしましょう。ストレージにはRDSを使用しており、IDは数値型でオートインクリメントされた値が上記ルームエンティティのルームIDに格納されます。ここでストレージがDynamoDBに変更され結果ストアで保持している値がオートインクリメントされた文字列型のハッシュ値に変更になったとします。この変化の影響はストアへのデータアクセス層、ドメインモデルだけにとどまらず、ドメインモデルを呼び出しているweb層、ビジネスロジック層へ影響が広がります。もしこのルームIDを独自の型として定義しアプリケーション内では独自定義された方RoomIdクラスを使用していた場合は、前述のストア変更の影響はデータアクセス層からドメインモデルまでに留めることが可能になります。
後編:ドメイン駆動設計+クリーンアーキテクチャ解説【クリーンアーキテクチャ編】
#4.参考文献
エリック・エヴァンスのドメイン駆動設計
ドメイン駆動設計の基本を理解する
The Clean Architecture
The Clean Architecture(翻訳)
持続可能な開発を目指す ~ ドメイン・ユースケース駆動(クリーンアーキテクチャ) + 単方向に制限した処理 + FRP