Clean ArchitectureとDDDの概念と得られるもの

architecture

Clean Architecture

関心事をレイヤーに分割し、依存方向を下位から上位に制限したアーキテクチャ。次のような円で説明され、中央のビジネスルールが上位、外側のフレームワークやドライバが下位となる。

依存方向を制限することで上位のレイヤーが下位のレイヤーの変更の影響を受けづらくなり安定させることができる。また、テストも書きやすくなる。

Clean Architecture

Web APIサーバーを想定して次のような関心事のレイヤーを切る。

  • handler: WAFとusecase間のデータ変換
  • usecase: アプリケーション固有のビジネスルール
  • entity: ビジネスデータとそれを操作するビジネスルール
  • repository: データへのアクセス

これらを図に当てはめると次のようになる。

レイヤーを当てはめる

依存方向逆転の原則(DIP)

ここで問題になるのがrepositoryの位置で、usecaseやentityよりも下位にあるので依存できず呼び出せない。これは呼ぶ側のレイヤーで定義したインタフェースをrepositoryで実装して渡すことで解決できる。

このように変化しやすい具象ではなく安定した抽象に依存することを依存方向逆転の原則(DIP)と呼ぶ。

依存方向逆転の原則

抽象に依存することでDBMSを変更などする際も、実装を置き換えるだけで済むといった説明がよくされるので、実際に置き換える機会がないと、コードが追いづらくなるだけで利点を感じづらいかもしれない。ただ、具象に依存している場合、DBMSの置き換えまでいかずとも、テーブルやカラムの扱いを変更すれば呼び出す側に影響を与えるわけで、そのような場合もDIPが有効にはたらくはずだと考えている。

また、具象に合わせて抽象を変更するのでは、結局不安定な具象に依存しているのと変わらないため、抽象は原則呼び出す側の必要によって定義され変更される必要がある。

技術的な決定の後回し

下位のリポジトリやフレームワークの決定は後回しにできるので、早速ビジネスルールの実装に入ることもできるわけだが、もしドメインが複雑で、かつ知見を持つドメインエキスパートがいるなら、まずはDDDの戦略的パターンによってそれを明らかにするところから始めると後述する恩恵が得られるかもしれない。

技術的な決定の後回し

ドメイン駆動設計(DDD)

ドメインエキスパートが普段使う言葉であるユビキタス言語を用いてドメインモデルを作る設計手法。ユビキタス言語を見つける過程でドメインの理解が深められ、モデルの境界を明確に定められるようになり、ドメインエキスパートとの合意も取りやすくなる。

コンテキスト

ドメインに含まれるサブドメインとそこで使われているユビキタス言語を見つけ出し、言葉の境界を定める。これによって明らかになった事業的に最も利益を生み出すコアドメインに優先的に投資する。

境界付けられたコンテキスト

境界付けられたコンテキストのユビキタス言語を用いてドメインモデルを作る。具体的にはドメインエキスパートの説明に登場する概念や振る舞いがそのままクラスやメソッドとして表現される。ユビキタス言語にない概念はモデルに含められない。もし不要な概念がモデルに持ち込まれているならコンテキストの境界を再考する。

コンテキストマップ

協力してお互いのニーズを満たすモデルを作る"パートナーシップ"や、下流のニーズが優先される"顧客/供給者の開発"、上流の提供するモデルを使うしかない"順応者"といったコンテキスト間の関係を図に表す。

モデルの変換が複雑な場合、“腐敗防止層(ACL)“というレイヤーに隔離するといった戦略が取られる。“公開ホストサービス(OHS)“を提供し、そこにリクエストするという関係もある。

コンテキストマップ

戦略的パターンと戦術的パターン

ここまでの手法がDDDの戦略的パターンで、ドメインモデルの実装に用いられるエンティティや値オブジェクト、集約、サービスなどを戦術的パターンと呼ぶ。ドメインエキスパートがいないと戦略的パターンが取れず、戦術的パターンのみを採用することもあるが、それは軽量DDDと呼ばれ、戦略的パターンを用いたときほどの見返りが得られないとされている。

エンティティと値オブジェクト

一意な識別子を持ち、可変なドメインオブジェクトをエンティティと呼び、識別子を持たず、不変なものを値オブジェクトと呼ぶ。エンティティはその識別子によって検索したり変更を追跡したりすることができるが、もしそれらが必要がなくその属性のみに関心がある場合、テストもしやすい値オブジェクトにすることが推奨されている。

エンティティの識別子としてDBのIDを用いることができるが、エンティティがテーブルの定義に引っ張られてはいけない。もし、エンティティがテーブルのデータをマッピングして、それにアクセスするゲッターやセッターを持つようなものであるなら、それはドメインが表現されていない"ドメインモデル貧血症"に陥っている。

集約

トランザクション整合性を持つエンティティや値オブジェクトの単位を集約と呼ぶ。つまり、一つのトランザクションでは一つの集約しか更新できない。集約が大きいとパフォーマンスに影響が出てしまうので、不必要に大きくならないようにしたい。もし複数の集約を更新する必要があるなら、結果整合性で問題ないか確認する。結果整合性の実現方法としては、ドメインイベントを発行しサブスクライバで非同期に更新するといったものがあり、失敗したらメッセージの再送によってリトライする。

(ドメイン)サービス

複数のドメインオブジェクトが関わるなどして、いずれかのエンティティや値オブジェクトに持たせるには自然でない処理を行う。アプリケーションサービスとは異なる概念で、トランザクションやセキュリティといったアプリケーションの関心事は持たない。

まとめ

  • Clean Architectureによってビジネスルールをその他の関心事による影響から守ることができる
  • DIPで上位のレイヤーが不安定な具象に依存することを避けられる
  • DDDによってドメインエキスパートから知見を学び、それをドメインモデルに表すことができる

参考

Clean Architecture 達人に学ぶソフトウェアの構造と設計

実践ドメイン駆動設計

4. Context Mapping - What Is Domain-Driven Design? [Book]