Arda Akgür
ソフトウェアアーキテクチャにおけるほとんどのアイデアは、何もないところから完全な形で現れるわけではありません。
エンジニアが学んだ原則は、別の問題に出会うと別の形を取ります。小さなアイデアが何年もかけて成長し——適用され、壊れ、再設計され——やがて独自のアーキテクチャアプローチになることもあります。
ARC-DDD の物語は、そうして始まりました。
私はオーストラリアで Associate Engineer として働いていた頃、このアプローチの最初のバージョンを開発し始めました。
しかし、このアイデアがどこから来たかを説明するとき、特に一人の人にクレジットを渡す必要があります。
Victorian Software CEO Adrian Budiman。
Adrian は、ドメインを古典的なアーキテクチャレイヤーとしてだけ考えるのではなく、システムを水平に分離したドメインに分割するというアイデアを、最初に私に示してくれた人でした。
そのアイデアが私の出発点になりました。
その後、私は自分で構築したシステムでこのアプローチをさらに推し進めました。
今日の ARC-DDD、あるいは私が時々呼ぶ Australian DDD は、その過程の結果です。
古典的な Domain-Driven Design は、通常システムを垂直レイヤーに分割します。
このアプローチは極めて価値があり、ARC-DDD はそれを排除しようとはしません。
私が加えたのは第二の軸です。
システムが垂直レイヤーに分割される一方で、ドメインモデル自体も水平の bounded context に分割されます。
例えば:
Core├── Users├── Events├── Transactions├── Vendors└── Paymentsここでの基本ルールは次のとおりです。
あるドメインは、別のドメインの世界全体を知るべきではない。
ここに ARC-DDD の本当の性格が現れます。
大規模システムでよく見る問題の一つが、巨大なエンティティモデルです。
例えば、システムに単一の User クラスがあります。
時間とともに、何もかもがそこに追加されていきます。
PasswordPhoneAddressBalanceIBANKYCPermissionsProfileSecurityTransactionsPreferences...すると Events ドメインは、ユーザーの名前と写真だけを表示したい場合でも、User モデル全体を使い始めます。
Transactions ドメインは残高と IBAN のために同じモデルを使います。
Vendor システムも再び同じモデルを使います。
結局、理論上はドメイン分離されているように見えるシステムが、実際には同じエンティティを通じて結びつく一つの巨大な構造になってしまいます。
ARC-DDD はここで異なるアプローチを取ります。
例えば:
Users.UserEvents.User2Transactions.User3これらは異なるドメインモデルです。
しかし、同じ物理データベーステーブルを表すことができます。
Users.User ──┐Events.User2 ──┼──> Users tableTransactions.User3 ──┘Events ドメインが必要とするのが次だけなら:
UserIdFirstNameFamilyNamePhotoUser2 はそれだけを含みます。
Transactions ドメインが次のフィールドを必要とするなら:
UserIdBalanceIBANVerifiedUser3 は自身のドメインの必要に応じて定義されます。
各ドメインは、自分が必要とする範囲でだけ世界を見ます。
これこそが ARC-DDD の中核的アプローチです。bounded context は分離されており、別ドメインの完全な集約を持ち込む代わりに、必要なフィールドを含むドメインローカルな投影を作ります。必要なら、その投影は同じ物理テーブルにマッピングされます。
ARC-DDD の重要な特徴の一つは、bounded context を作るために別々のデータベースを必須とはしないことです。
例えば、Entity Framework Core では:
modelBuilder .Entity<User2>() .ToTable("Users") .HasKey(x => x.UserId);Events ドメインの User2 は、物理的な Users テーブルに引き続きバインドできます。
しかしモデルには Events ドメインが必要とするプロパティだけが含まれるため、そのドメインは不要なユーザーフィールドを自モデルに引きずり込みません。
公開参照実装(eski-dev)の Goldtag の例では、User、User2、User3 が異なるドメインの視点から同じ Users テーブルにバインドします。User2 は Events が必要とするデータと振る舞いを表し、User3 は Transactions が必要とするものを表します。
これによって重要なバランスが得られます。
データベースは物理的に共有できるが、ドメインモデルは共有する必要がない。
これは ARC-DDD の中核原則の一つです。
この分離はフォルダレベルで終わりません。
各 bounded context は独自の DbContext 構造を持ちます。
例えば:
UserDbContextEventsDbContextTransactionDbContextVendorsDbContextEvents ドメインは自身の投影モデルを知っています。
Transactions ドメインは自身のモデルを知っています。
Users ドメインは正規の User モデルを所有します。
公開参照実装(eski-dev)では、例えば UserDbContext は正規の User モデルを使い、EventsDbContext は User2 投影を使い、TransactionDbContext は自身のコンテキスト内で User3 投影を使います。
そのため、あるドメインは別ドメインの Core モデルを直接インポートしません。
Events の内部では:
using Gold.Core.Users;User 集約全体をシステムに引き込む代わりに、Events は自身の投影を作ります。
これは意図的な重複です。
なぜなら ARC-DDD では、時として:
少量の重複は、強い結合よりも安い。
Core レベルでの中核ルールの一つは、ドメインフォルダ同士が直接インポートしないことです。例えば Events は Gold.Core.Users を使う代わりに、User2 のような独自のローカル表現を作ります。
実際のアプリケーションでは、bounded context は完全な孤立状態では生きられません。
単一の操作が同時に複数のドメインを必要とすることがあります。例えば:
ARC-DDD はここでドメイン層同士を接続しません。代わりに、合成をシステムの外側の縁へ移します。
例えば:
API | +--> OrderingRepository | +--> PaymentsRepository | +--> UsersRepositoryつまり、クロスドメインのオーケストレーションは API またはアプリケーション層で行われます。
Core ドメイン同士はインポートし合わず、依存の鎖を作りません。
eski-dev でも、クロスドメインの合成は API 側で行われます。コントローラーやアプリケーションサービスは必要に応じて複数のリポジトリと連携できますが、Core ドメイン同士は配線されません。
この分離は、システムが大きくなるにつれて非常に価値を持ちます。
もし今日新しい ARC-DDD システムを作るなら、基本構造はかなりシンプルに保ちます。
Product.CoreProduct.DomainProduct.Api依存の方向:
Product.Api ↓Product.Domain ↓Product.CoreCore は純粋なドメインモデルを含みます。
Entity Framework やインフラへの依存はありません。
Domain は永続化層、DbContext、リポジトリ構造を含みます。
Api は composition root として振る舞い、必要に応じて同じユースケース内で異なる bounded context をオーケストレーションします。
eski-dev の現在の参照構造における依存方向も Gold.Api → Gold.Domain → Gold.Core です。Core 層をインフラパッケージから自由にしておくことは、基本ルールの一つです。
今日このアーキテクチャを一文で表すなら:
ARC-DDD は、古典的なレイヤード DDD を、水平の bounded context と共有テーブル上のドメイン固有エンティティ投影で拡張するソフトウェアアーキテクチャアプローチである。
より技術的に次のように定義しています。
古典的レイヤード DDD + Core 内の水平 bounded-context フォルダ + 共有テーブル上のコンテキストごとのエンティティ投影。
しかし私にとって、このアプローチの背後にあるより重要な考えは次です。
ドメインは、システムに存在するすべての知識を見るべきではない——自分の仕事をするために必要な世界だけを見るべきだ。
私にとって、このアプローチの起源はオーストラリアに遡るからです。
Associate Engineer として働いていたときに Adrian Budiman から学んだ水平ドメイン分離のアイデアが、出発点でした。
その後、ドメインローカルなエンティティモデル、共有テーブル投影、ドメインごとの DbContext、制御されたクロスドメイン合成の原則とともに、自分のシステムに適用してそのアイデアを広げました。
だから ARC-DDD について話すとき、それを「すべてをゼロから発明した」と提示するのは正しくないと思います。
エンジニアリングは、そもそも普通はそう進みません。
誰かが強いアイデアをくれる。
それを別の問題に適用する。
何が足りないかを見る。
その上に新しい原則を足す。
そして何年も後に、元のアイデアとはかなり異なるシステムが現れる。
私にとって ARC-DDD は、まさにそのように発展しました。
Adrian が示してくれた水平ドメイン思考が火花でした。
私の貢献は、その火花を、今日私が ARC-DDD と呼ぶ実践的なアーキテクチャモデルに変えたことです。
ARC-DDD は、私が理論だけを語るアーキテクチャアプローチではありません。
このアプローチが実システム内でどう適用されるかを見たい人のために、私の古い開発コードベースの一つを公開しています。
GitHub:
https://github.com/Arcnaboo/eski-dev
このリポジトリは公開されており、C# ベースで、MIT ライセンスの下で公開されています。
このリポジトリは、ARC-DDD の公開 Goldtag legacy backend reference implementation です。
リポジトリは、参照実装を明示的に次のように定義しています。
Gold.CoreGold.DomainGold.Apiリポジトリ内では、特に次のモデルを見ることをおすすめします。
Gold.Core/Users/User.csGold.Core/Events/User2.csGold.Core/Transactions/User3.csこれらは ARC-DDD のもっとも重要なアイデアを直接示します。
正規の User は Users bounded context に属します。
Events は、同じユーザーに対して自身の必要に合わせて絞り込んだ User2 モデルを使います。
Transactions は、自身の操作に適した User3 モデルを使います。
同じアプローチは Event と Event2、Notification と Notification2 のようなモデルでも見られます。参照実装を学ぶときは、リポジトリ内のこれらのファイルを特に見てください。
ここで重要な区別をする必要があります。
このリポジトリは、古くて実際の本番コードベースの痕跡を帯びています。
だから、今日ゼロから書くシステムで、そこに含まれるすべての実装詳細を一対一で使うことはしません。
例えば、古い接続文字列のやり方、一部のコントローラー構造、不完全な依存性注入の使い方、もはや不要なレガシー部分は、ARC-DDD そのものではありません。
特にこの違いを指摘しています。
アイデアを取り入れ、仕組みは現代化せよ。
残るアイデアは:
水平 bounded context + ドメインごとの投影 + ドメインごとの DbContext。
古いコードの歴史的な実装詳細ではありません。
アーキテクチャアプローチを図だけで説明するのは十分ではないと思います。
人々は次のことができる必要があります。
だから私は、ARC-DDD のアーキテクチャドキュメントと、実際の公開参照実装の両方がアクセス可能なままであることを望んでいます。
リポジトリ:
https://github.com/Arcnaboo/eski-dev
しかし、ARC-DDD を理解するためにリポジトリのすべての行をコピーしてほしいわけではありません。
むしろその逆です。
コードの時代から来ている部分を分けてください。
ドメイン境界を見てください。
同じ物理エンティティが、異なる bounded context でどのように異なる形でモデル化されているかを見てください。
コンテキストがどう分離されているかを見てください。
なぜ Core が他ドメインのモデルをインポートしないのかを見てください。
なぜなら ARC-DDD の本質は、フレームワークでも特定の技術でもないからです。
Entity Framework は変わり得ます。
.NET は変わり得ます。
データベースは変わり得ます。
リポジトリパターンは変わり得ます。
何年後かには、今日使っているほとんどのツールも変わっているかもしれません。
しかし基本原則は変わりません。
ドメイン境界を、コード上の本当の境界にせよ。
そして各ドメインには、それが必要とする世界だけを見せてください。