DDDからみた基盤となるアーキテクチャ

去年の夏前くらいにDDDまわりの勉強していた。でこのメモを書いていたので少し直して晒してみる。

DDDはアプリケーション設計のための要件整理の話だと思う。 ただ実装も話題にしてしまっていてアーキテクチャとかの話も出てきたりと混乱している。なので自分なりに整理してみる。 さらに混乱に寄与したらごめんなさい。

まずDDD関係の資料とDDDの文脈で使われるアーキテクチャの意味を説明する。 次にDDDで言われる2つのアーキテクチャの1つである 基盤となるアーキテクチャ をDDDのコンテキスト、コンテキストマップと合わせて説明する。 最後に 基盤となるアーキテクチャ に分類できるアーキテクチャを列挙する。

ドメイン駆動設計(DDD)の資料は次の3冊を元にした。

書籍 初版年度 内容
DDD本 2003 DDDを提案: アプローチやパターンなど整理してる
IDDD本 2013 具体的に実装する説明を補足と自認: DDDで使う典型パターンにイベントを追加、色々なアーキテクチャとの関わりを記載
FRDM本 2016 ドメインモデルのFunctional/Reactiveな実装例を示した

ドメイン駆動設計(DDD)はソフトウェア設計論の1つ。

アプローチの特徴としてドメインモデル(ビジネス知識)の抽象化を中核にして設計することが挙げられる。 また設計されるソフトウェアの構造に現れるパターンを挙げるだけでなく、適切なドメインモデルを導くための方法論から開発チームとドメインエキスパート(ユーザーや営業)の関係構築、チームビルディングや開発プロセスについても言及している。

DDDはに上記の最初の2冊で出てくる話題には触れられそう。

時間がなければIDDD本を読むのが次の点もあって良いと感じる。 1つはDDD本よりもあとで整理されたパターン(ドメインイベント)が追加されていること。 もう1つは、要件の整理の話のあとで実装するときのアーキテクチャ選択も扱うので想像しやすかったから。 クリーンアーキテクチャなどに現れる 依存性逆転の原則 も紹介しているから。ただ最初に書かれていた要件整理としてのDDDから遠のいてはいる。

DDDはオブジェクト指向言語での開発を暗黙に仮定している気配がある。 今回はDDD関係の本で出てきたアーキテクチャを整理したいのでDDDの中身は範囲外とする。

掲載箇所に関する言葉の選び方

「DDDでは」といった表現を使う場合、DDD本IDDD本が提示したりコミュニティの合意っぽいものを指すことにする。また前述の2冊の どちらか/両方 で記載されていることを示すのに「DDDに記載されている/提示された」と書く。

経過を考慮する意図があり一方に限定する場合は、DDD本IDDD本と「本」を付けることにする。

DDD本でアーキテクチャと呼ぶもの

DDD本では「アーキテクチャ」という用語を「技術的な関心をアプリケーション・ドメイン・ビジネス的な関心から分離するためのソフトウェアの構造」としている。

IDDD本でアーキテクチャと呼ぶもの

IDDD本では2つの意味でアーキテクチャという言葉が出てくる。

1つめはDDD本と同様の意味(技術的関心とドメインを分離するための構造)での使っている。 一方で コンポーネント間の連携パターン という別の意味でもアーキテクチャという言葉を使っている。

この連携パターンとしてのアーキテクチャはDDDと関係ないところで生まれているがDDDに取り入れる事ができるという立場で紹介している。

特に、ドメイン・ビジネス的な関心をコンポーネントとして独立させて(1つめのアーキテクチャ)、それらを連携(2つめのアーキテクチャ)させると良いと説明してい。

この考えはサービスオリエントアーキテクチャ(SOA)マイクロサービスに繋がっていくように感じた。

ここでは1つ目の意味でのアーキテクチャを 基盤となるアーキテクチャ と呼び2つめのアーキテクチャを 連携パターンとしてのアーキテクチャ と呼ぶことにする。

ここまでコンポーネントという言葉を使ってきた。 このコンポーネントをDDDではサブコンテキストと呼ぶ。 サブコンテキストはDDDのモデリング(設計方法)で大きな役割を持っている。

アーキテクチャを扱いたい。だけど、アーキテクチャをDDDのサブコンテキストと関連付けて整理しいので、先にDDDのモデリングについて簡単に紹介する。

DDDのモデリング<戦略/戦術>

DDDでのモデリングは戦略的モデリングと戦術的モデリングの2つを通して行う。

戦略的モデリングは扱っている関心や概念のグルーピングとグループ同士の関係を扱っている。 一方で戦術的モデリングはグループ内にある概念の分類や包含関係・ライフタイムなどを扱う。

ちなみにDDD本には戦略的モデリングは戦術的モデリングより影響範囲が大きくより簡潔さを求められるため戦術と分離したと書かれている。 そして戦略的モデリングも戦術的モデリングと同じようにコードを書く人がやるべきだと明記されている。

戦術的モデリングはDDDとアーキテクチャの関係にはあまり関わらないため、今回は戦術的モデリングの詳細には触れない。

DDDの戦略的モデリング

戦略的モデリングではドメインとコンテキストという2つの側面でアプリケーションを捉える。

この2つを発見することが大雑把な設計やリファクタの指針として機能する。 ドメインは問題の分類名で、一方でコンテキストは解決手段を実装したモジュールやコンポーネントである。

ドメイン

アプリケーションが果たすべき役割と伴う知識体系や認識体系を ドメイン と呼ぶ。 そしてアプリケーションはより細かい役割が連携して構築される。 この分割された小さいドメインをサブドメインと呼ぶ。

コンテキスト

アプリケーションはさまざまなコンポーネント(部品)で組み立てられている。 そのコンポーネントが持つ論理や概念の所属するカテゴリコンテキスト と呼ぶ。 コンテキストも同様にサブコンテキストに分解できる。

ドメインとコンテキストは似ているけど 違うドメインは役割や問題 に対応付けられ コンテキストは実装や解決手段 に対応付けられる。 違いを強調して、ドメインは問題空間に対応しコンテキストは解決空間に対応すると言ったりする。

コンテキストは実装に対応するので、未来の理想的なシステムや現在のシステムに合わせて設定される。

現状のコンテキストマップ 現状のコンテキストマップ

この図は、現在のシステムに合わせてコンテキストマップを作った場合の具体例になる。 ドメインとコンテキストが交差してしまっている。

一般に、(サブ)コンテキストと(サブ)ドメインは1対1で対応するのがわかりやすく扱いやすいと考えられている。

対応しているコンテキストマップ 対応しているコンテキストマップ

コンテキストマップ

それぞれのコンテキストは提供サービスや扱う概念が違う。 それらが協調してさらに大きなコンテキストとして振る舞う。

例えば、GUI機能や複数アプリケーションを統合するUIは他のサブコンテキストと連携する独立したサブコンテキストとしてみなせる。

またサブコンテキスト間の連携で必要なデータ変換を別の小さなサブコンテキストとしてみなすこともある。

この関係を図示したものをコンテキストマップと呼ぶ。

コンテキストマップ コンテキストマップ

理解や合意のために作られるゆるい概念図であり、コンテキストマップにはドメインも書き込まれたりする。

コンテキスト間の関係についての洞察もDDDでは触れられている。 しかし必要なコンテキストについて説明が済んだので、ここでは 基盤となるアーキテクチャ について話をすすめる。

基盤となるアーキテクチャ

IDDD本で 基盤となるアーキテクチャ の様な意味で言及されているアーキテクチャがあると書いた。 これらは技術的な関心をドメイン知識から分離して独立したコンテキストを組み立てやすくしている。 その様な効果があると期待されているアーキテクチャを列挙してみる。

アーキテクチャ 時期
レイヤードアーキテクチャ(?) ?(昔からあるし)
ヘキサゴナルアーキテクチャ 2008-06-19
オニオンアーキテクチャ 2008-07-29
クリーンアーキテクチャ 2012-08-13

レイヤードアーキテクチャはIDDD本でも不完全になりやすいことが記載されていたので (?) をつけた。

レイヤードアーキテクチャ

古くからある形のアプリケーションアーキテクチャ。 各コンポーネントや機能が複数のレイヤーに所属する。それぞれの層からは直下のレイヤにのみ依存するものを指す。 DDD本ではUI・アプリケーション・ドメイン・インフラの4層で構成したものを紹介していた。

レイヤードアーキテクチャ レイヤードアーキテクチャ

このアーキテクチャの特徴はドメインレイヤがインフラに依存するため完全に切り離せていないところ。 ちなみに狭義には各レイヤーは直下のレイヤーにしか依存しないらしいが、下位レイヤーであれば直下以外も許容するものもレイヤードアーキテクチャと呼ぶ。(そんな用語にこだわる必要はない)

ヘキサゴナルアーキテクチャ

ヘキサゴナルアーキテクチャではレイヤードアーキテクチャに 依存性逆転の原則 を適用して、ドメインを表現したコードがインフラに依存するを防いでいる。

ヘキサゴナルアーキテクチャ ヘキサゴナルアーキテクチャ

アプリケーションの構成要素のうち状態変化や同一性があるオブジェクトは永続化や検索が求められたりする。 DDDでこれらを担うことが多いのはリポジトリと呼ばれるオブジェクトになる。

これらの処理はアプリケーション自体ではなくデータベースやファイルシステムといったインフラ・ミドルウェアを利用することが多い。 そして依存先を変更する可能性があり、またプログラムよりも長い期間で管理が必要になる。

そこでインフラに依存した処理を書かずにインタフェースやプロトコルなどを定義して外部から与えられるようにする。 これによってインフラ依存をアプリケーションから取り除きアプリの実装を簡素化できる。よくDIとか呼んだりするやつだ。

ヘキサゴナルアーキテクチャでは、インフラ依存コードの他にUIも同様に、ドメインが定義したインタフェースの実装として接続する。 そしてどちら(UI,インフラ)の実装もアダプターと呼ぶ。

UIや外部サービスやインフラへの接続を受け持つのがアダプターで、アプリケーション経由でドメインに渡す。

オニオンアーキテクチャ

解説記事がある。 ヘキサゴナルアーキテクチャと同様 依存性逆転原則 を利用する。 それに加えて用語やコンセプトの配置を厳格に決めている。これにより実装上の混乱を減らしているとのこと。

オニオンアーキテクチャ オニオンアーキテクチャ
layer definition
Domain Model layer where our entities and classes closely related to them e.g. value objects reside
Domain Services layer where domain-defined processes reside
Application Services layer where application-specific logic i.e. our use cases reside
Outer layer which keeps peripheral concerns like UI, databases or tests

アプリケーションコアがドメイン・ドメインサービス・アプリケーションサービスと3層に分離されている。 ドメインとドメインサービスを別の層として分けている。

ドメインサービスは複数のエンティティや集約を受け取る処理を表す概念なので、ドメインの構成要素であるエンティティ・値・集約にのみ依存する。(なので自然と別れてると言える)

しかしドメインサービスはドメインに所属する知識として扱われるのが元々のDDDなのでレイヤを分けるのは不自然な気がした。

クリーンアーキテクチャ

アプリケーションの Use Cases で要求内容をインタフェースで定義して Interface Adapter 層でインフラやUIと繋ぎこむ。 Interface Adapter には幾つか典型的な用途(Gateway, Presenter, Controller)も定義している。

元の記事だと Use Cases がインタフェースを定義し、 Interaface Adapter が実装している。

クリーンアーキテクチャ クリーンアーキテクチャ

外側のエンティティが直下のレイヤに依存するのが原則になっている。 ここにDDDの用語を使ってる感がとてもあるけど Entities がDDDのドメインレイヤに対応している。

下の通り、エンティティの他に値やドメインサービス・ドメインイベント・仕様・集約などが入ると思われる。 迷ったのはライフサイクルの典型的なパターンであるファクトリ・リポジトリの位置になる。 インフラへの依存を逆転させる必要があり Use Cases でインタフェースとして定義するのが自然だと思われる。 また Entities に含まれるエンティや集約を参照するので依存関係に混乱も生み出さないで済んでいる。

Entities encapsulate Enterprise wide business rules. An entity can be an object with methods, or it can be a set of data structures and functions

文を読むとオブジェクティブ(object with methods)だけじゃなくファンクショナル(set of data structures and functions)な実装も考慮していたようだ。

ヘキサゴナル、オニオン、クリーンアーキテクチャのどれも 依存性逆転の原則 を用いてインフラや外部サービスへの依存をドメインから取り除いている。 これによりサブコンテキスト内のドメインを独立させながら連携させたりインフラやUIと繋げられる。

また実装例と解説がgithubにあった解説がある。 ここで Architectural ApprochArchitectual Reactive Approach の図が書かれている。

Architectual Reactive Approach のなかで、 Observable, Observable, Susscriver とインタフェース定義が流れている。 Stream の流れが変更イベントの流れを表している。

これは、データベースへの書き込みなどの変更イベントを検出するためにオブザーバブルを受け入れるユースケースを定義したり、ユースケースから変更を通知できるサブスクライバーを登録できるようにすることで、クリーンアーキテクチャとリアクティブを両立できることを書いてるっぽい。

感想

DDDのコンテキスト・ドメインの説明から始めて、クリーンアーキテクチャなど 基盤となるアーキテクチャ に触れてみた。 依存性の逆転についてだけど、ドメインはインフラに依存しないのだから依存させないというの主張は弱い。 頻発する変更の影響が広がってしまう依存関係を避けるのが大事という方が自然に思われる。

ユニットテストでストレージを使わないで済むから便利という感じに思える。 CIが定着していたりDocker Compose, k8sと結合テストを行いやすい現状なら、それで済ませても良い気がする。 とりあえず、プロトタイプ作成からインタフェースなどで依存関係を逆転させるのは避けたい。

「ここは外部に依存しているからインタフェースやDI経由にしやすい様に関連機能をまとめておこう」くらいがいい。 モックを利用したテストや可搬性の改善が必要になるまで待っても不便しないためだ。

まだ価値やメンタルモデルが作れていない初期フェーズの時の自分の手の動かし方を考えてみた。

  1. プロトタイプ時: ドメインを意識しつつ直接的に書く
  2. プロトタイプ・リファクタ時: ドメインモデルの整理
  3. プロトタイプ・リファクタ時: 関連の強い依存コードを自然に集約させられるドメインモデル内での場所を決めて集約する
  4. alpha/beta リリース: 集約した部分をインタフェースなどに変更して既存実装を壊さない様にテスト可能にする

まぁ、普通だ。

一番大事なのはアプリケーションのドメイン(論理的なモデル)に対応していてかつ関連の深いコードは近くに置かれている状態を目指すことである。 それを実現するために(特に整理されていない時に)コードは何度も変更される。 その時点でインタフェースなどで変更箇所を増やしてしまうと扱いにくくなり避けるのが良い。

最初から答えが見えるなら飛びつけば良い。その方が効率的だ。整理されてないのであれば試行錯誤しやすくするのが良い。

クリーンアーキテクチャはメモを書いて寝かしている間に日本語訳が出ていたりブームが来て解説記事が爆増していた。 個人的にはコアを知ってればあとは適当にって思ってしまうけど目を通すと理解の助けになりそうだなぁと思った。

最近とは限らないけどクリーンアーキテクチャ周辺の詳しい説明やサンプル実装の一覧を残しておく。

ちなみに最初のDDD本では依存性の逆転は言及されていない。またアプリケーションの要件とコードの対応づけは自由にやれって感じ。それはそう。

comments powered by Disqus