最近、DDD(ドメイン駆動設計)について考えていると、どうしてもぶつかる壁がある。「参照系と更新系の非対称性」と、そこから生まれる「理想と現実の乖離」だ。
ドメインモデルは本来、ビジネスの世界をコードとして表現したいはずなのに、実用的なパフォーマンスを追い求めると、コード上の表現力がどんどん痩せ細っていく。この矛盾にどう折り合いをつけるか、最近の思考の整理。
理想:関係性の完全な表現
DDDを学び始めた頃、誰もが夢見るのが「Pureなドメインモデル」だ。 そこでは、すべてのオブジェクトが有機的に繋がっている。
// 理想的な世界 class Order { // 注文から顧客へ、オブジェクトとして参照を辿れる customer: Customer; lines: OrderLine[]; // ロジックの中で自然に関係性を使える validate() { if (this.customer.isPremium()) { ... } } }
これは「宣言的」で美しい。コードを見ればドメインの関係性が一目瞭然だからだ。しかし、これをRDBの上に実装しようとした瞬間、地獄を見る。
現実:ID参照という妥協
パフォーマンス、トランザクション境界、メモリ効率。これらを考慮した「実用的DDD」では、集約(Aggregate)の境界を厳密に切り、他への参照は「ID」で行うことが鉄則になる。
// 実用的な世界 class Order { // 参照を切る。あるのはIDだけ。 customerId: CustomerId; lines: OrderLine[]; // 必要なデータはメソッド引数として渡してもらう validate(customerStatus: CustomerStatus) { ... } }
これでパフォーマンス問題は解決する。しかし、代償として**「コードからドメインの地図が消える」**。
Order クラスを見ても、それが Customer とどういう関係なのか、直感的には分からなくなる。「ドメインをコードで表現する」と言いつつ、実態はIDの管理屋になっていないか?という虚無感が襲ってくる。
矛盾:宣言的でありたい vs 実用的でありたい
ここで生じているのは、以下の対立だ。
- 宣言的欲求(Ideal): ドメインの関係性や構造を、静的な型やコードとして「完全な形」で定義しておきたい。ドキュメントではなく、動く仕様として。
- 実用的制約(Pragmatic): 更新時のロック競合や無駄なRoadを防ぐために、モデルはバラバラに分断しておきたい。
「実用」を取ると「ドメインの全体像」が見えなくなり、「理想」を取ると「システム」が死ぬ。DDD自体が矛盾を抱えているようにさえ思える。
解決策:GraphQLを「理想のドメイン層」と見なす
この矛盾を解消するミッシングリンクとして、GraphQLが非常にハマるという結論に至った。
アーキテクチャのレイヤーを一つ増やし、役割を分担させるのだ。
- バックエンド実装(更新系): 徹底的に「実用的DDD」に徹する。ID参照、塩漬けのデータ構造、小さな集約。ドライに徹する。
- GraphQLスキーマ(インターフェース): ここに「理想的DDD」を記述する。
スキーマ=生きたドメイン定義書
バックエンドの実装では customerId しか持っていなくても、GraphQLのスキーマ上では堂々とリレーションを張る。
# ここに「理想」を書く type Order { id: ID! # 実装上はIDしかなくても、スキーマではCustomerと繋がっていると宣言する customer: Customer! lines: [OrderLine!]! } type Mutation { # CRUDではなく、ドメインの「振る舞い(ユースケース)」を定義する shipOrder(id: ID!): OrderPayload }
こうすることで、以下のメリットが生まれる。
- 地図の復活: 開発者はスキーマ(
schema.graphql)を見れば、ドメインの全容と関係性を理解できる。ドキュメントと違い、これは嘘をつかない(嘘なら動かないから)。 - 実装の隠蔽: Resolverが
customerIdを使ってよしなにデータを引いてくればいい(DataLoaderを使えばN+1も起きない)。裏側がどれだけ泥臭いID参照で最適化されていても、外からは「リッチなドメインモデル」に見える。 - CQRSの自然な導入: GraphQLはQuery(参照)とMutation(更新)が明確に分かれているため、DDDの推奨するCQRSと相性が良すぎる。
結論
「ドメインの関係性をコードで表現したい」という欲求を、バックエンドのEntityクラス(実装詳細)だけで満たそうとするから辛くなる。
「関係性の表現(宣言)」はGraphQLスキーマに任せ、「整合性の確保(実用)」はバックエンドの集約に任せる。
まあ、そこそこ良い回答かなとは思う