質問: ドメインモデル(ドメイン駆動設計)では、集約ルートにはグローバル空間で一意なIDを付けるべし、という情報を得ました。 DDD本にも、「(集約)ルートエンティティはグローバルな同一性を持つ」という記述があります。 ※ごめんなさい、電子書籍なのでページ番号分かりません。集約の不変条件についての記述です。 このことについて疑問がありますので教えて下さい。 例えば、プロジェクト管理アプリケーションを作っているとします。 ざっくりとした要件はこんな感じです。 1. プロジェクトを複数管理できる 2. ひとつのプロジェクトは複数のタスクから構成される 3. 人員を複数管理できる 4. 人員は、色々なプロジェクトのタスクに割り振られる こうすると、集約としては「プロジェクト」と「人員」が出てくるかなと思います。 どちらも「プロジェクトID」と「人員ID」という、グローバル空間で一意なIDを付ければ良さそうです。 また、「タスク」は「プロジェクト」の集約に含まれそうなので、そういったIDは付けません。 ここからが本題なのですが、もしこのアプリケーションをクラウドサービス化したとしたらどうなるでしょうか。 「プロジェクト集約」とか「人員集約」とかは、サービスの契約者ごとに管理されなければなりません。 つまり、「サービス契約者ID」と「プロジェクトID」が揃わなければ、「プロジェクト」が特定できません。 これでは「プロジェクト集約」はグローバルなIDを持たないことになりますが、これは許されるのでしょうか? あるいは、「プロジェクト集約ルート」は集約ルートからただのエンティティに降格するのでしょうか。 そもそも集約ルートがグローバルなIDで管理されなければならない、というのがいまいちピンときていないのですが・・・ 上位の集約のID(上記例ではサービス契約者ID)にぶら下がるのでは、ダメなのでしょうか? それとも、DDD本にある「グローバルな同一性」とは、単一のIDを要求するものではなく、「サービス契約者ID+プロジェクトID」という複合的なIDであっても、OKということでしょうか?
回答: エンティティの識別子は、複合IDでも良いと思います。 具体的には、以下のように ProjectId を値オブジェクトとして定義できます。(Python の場合) ``` class ProjectId(ValueObject): def __init__(self, tenant_id, sub_id): self.tenant_id = tenant_id self.sub_id = sub_id def __str__(self): u""" 文字列表現 """ return "{}_{}".format(self.tenant_id, self.sub_id) class Project(Entity): def __init__(self, identity, ...): # identity は ProjectId 型 self.identity = identity ``` ただ、 @masuda220 さんの言うように「『グローバルな同一性』というのは、『グローバルなID』とは等価ではない」ものの、**実用的には、プロジェクトの識別子をアプリケーション全体で一意としたほうが、アプリケーションの設計がシンプルになる**でしょう(上記のような複合IDも不要になる)。 ここからは経験談ですが、 DynamoDB で同様のアーキテクチャを実装していて、 hash_key, range_key に「サービス契約者ID」と「プロジェクトID」を設定し、複合IDを定義していたのですが、最近、その設計を改めました。Project テーブルの primary key をハッシュキー型に変更し、これまで primary key としていた (tenant_id, project_id) を Global Secondary Index に置き換え、 project_id をグローバルに一意とすることで、随分と設計がシンプルになりました。 ご参考になると幸いです。 ### 追記 やや本論からは話がそれますが、「サービス契約者」を管理するコンテキスト(例:アカウント管理のコンテキスト)と、「プロジェクト」を管理するコンテキスト(タスク管理のコンテキスト)が異なる場合は、後者のコンテキストにおいては「サービス契約者」の概念をコンテキストに適したユビキタス言語で表現しなおして、値オブジェクトとして扱うと便利です。