6746


ドメインモデル（ドメイン駆動設計）では、集約ルートにはグローバル空間で一意なIDを付けるべし、という情報を得ました。  
DDD本にも、「（集約）ルートエンティティはグローバルな同一性を持つ」という記述があります。  
※ごめんなさい、電子書籍なのでページ番号分かりません。集約の不変条件についての記述です。  
このことについて疑問がありますので教えて下さい。

例えば、プロジェクト管理アプリケーションを作っているとします。  
ざっくりとした要件はこんな感じです。

  1. プロジェクトを複数管理できる
  2. ひとつのプロジェクトは複数のタスクから構成される
  3. 人員を複数管理できる
  4. 人員は、色々なプロジェクトのタスクに割り振られる

こうすると、集約としては「プロジェクト」と「人員」が出てくるかなと思います。  
どちらも「プロジェクトID」と「人員ID」という、グローバル空間で一意なIDを付ければ良さそうです。  
また、「タスク」は「プロジェクト」の集約に含まれそうなので、そういったIDは付けません。

ここからが本題なのですが、もしこのアプリケーションをクラウドサービス化したとしたらどうなるでしょうか。  
「プロジェクト集約」とか「人員集約」とかは、サービスの契約者ごとに管理されなければなりません。  
つまり、「サービス契約者ID」と「プロジェクトID」が揃わなければ、「プロジェクト」が特定できません。

これでは「プロジェクト集約」はグローバルなIDを持たないことになりますが、これは許されるのでしょうか？  
あるいは、「プロジェクト集約ルート」は集約ルートからただのエンティティに降格するのでしょうか。

そもそも集約ルートがグローバルなIDで管理されなければならない、というのがいまいちピンときていないのですが・・・  
上位の集約のID（上記例ではサービス契約者ID）にぶら下がるのでは、ダメなのでしょうか？  
それとも、DDD本にある「グローバルな同一性」とは、単一のIDを要求するものではなく、「サービス契約者ID＋プロジェクトID」という複合的なIDであっても、OKということでしょうか？


CC BY-SA 3.0
2015-02-17T05:52:34.330
6620
2015-02-22T01:24:38.403
8078
question
ドメイン駆動設計
ドメインモデルにおける集約ルートには、グローバル空間で一意なIDが必要？


こんにちは。

> ここからが本題なのですが、もしこのアプリケーションをクラウドサービス化したとしたらどうなるでしょうか。  
>  「プロジェクト集約」とか「人員集約」とかは、サービスの契約者ごとに管理されなければなりません。  
>  つまり、「サービス契約者ID」と「プロジェクトID」が揃わなければ、「プロジェクト」が特定できません。

ここに答えがでていますね。答えは要件次第ですね。要件によって構築されるユビキタス言語の世界観によってドメインモデルはいくらでも選択肢があります。いきなりドメインモデルを考えるまえにそのモデルが使われる目的や意図について考えたほうがよいですね。DDDにも声に出してモデリングするという話が出てきますが、ユーザストーリのような文章にして5W1Hを明確にしてみるとよいかもしれません。

ということで、サービスの契約者というのがどういう概念を表すかはそれを語らなければ、伝わりませんからね。

ここからは僕の勝手な妄想としてお読みください。  
自社が提供する、マルチテナンシーなクラウドサービスを考えると、以下のようなストーリがありえそうです。

  * システム管理者は、顧客を作成できる。同時に顧客管理者が作成される。
  * 顧客管理者は、自顧客を更新できる
  * 顧客管理者は、ユーザを追加できる
  * 顧客管理者は、顧客IDを指定して、複数のユーザを取得できる
  * 顧客管理者は、ユーザIDを指定して、ユーザを取得できる
  * 顧客管理者は、ユーザIDを指定して、ユーザを更新できる
  * 顧客管理者は、ユーザを削除できる
  * 顧客管理者は、プロジェクトを作成できる
  * ユーザは、顧客IDを指定して、複数のプロジェクトを取得できる
  * ユーザは、プロジェクトIDを指定して、プロジェクトを取得できる
  * ユーザは、プロジェクトIDを指定して、プロジェクトを更新できる
  * 顧客管理者は、プロジェクトIDを指定して、プロジェクトを破棄できる
  * ユーザは、プロジェクトIDを指定して、タスクを作成できる
  * ユーザは、プロジェクトIDを指定して、複数のタスクを取得できる
  * ユーザは、タスクIDを指定して、タスクを取得できる
  * ユーザは、タスクIDを指定して、タスクを更新できる
  * ユーザは、タスクIDを指定して、タスクを破棄できる

ドメインの観点でいうとグルローバルな識別子を持つエンティティで、ライフサイクルの観点でいうとグローバルな識別子を持つ集約は、以下になるのではないでしょうか？

  * 顧客
  * 顧客管理者
  * ユーザ(人員？)
  * プロジェクト
  * タスク

> また、「タスク」は「プロジェクト」の集約に含まれそうなので、そういったIDは付けません。

このようにしてしまった場合、タスクはプロジェクトを説明する属性になります。当然ですが、タスクは独立して存在することが基本的にはできません。プロジェクトを特定してからタスクを検索することにななると思います。これは概念的な塊とか境界の定義の話ですが、技術的や性能上の観点と観ると、集約はデータを取り扱う境界にもなるので、タスク単独でI/Oはできずプロジェクト単位でしかI/Oできません。僕もいつも悩むところですが、概念と性能の二つの側面でトレードオフがありますね。

次にタスクをエンティティにするか、値オブジェクトにするかという話ですが、仮にタスクを値オブジェクトにしてしまった場合は、あるプロジェクト内で目的のタスクを検索することは不可能になります。タスクが持つ属性(タスク名や説明など)は時とともに変遷するので特定ができなくなります。特定するようなモデルは識別子を持つエンティティにすべきですね。  
エンティティにも、グローバルな識別子を持つエンティティ(グローバルエンティティと呼びことが多い)と、集約内でローカルな識別子を持つエンティティ(ローカルエンティティと呼ぶことが多い)があります。  
さて、今回の場合どちらが好ましいでしょうかね？

ストーリから素直に導くと、グローバルなエンティティになるのではないでしょうか？(個人的な見解)  
タスクを扱うのに、必ずしもプロジェクト情報が必要かというとそうでもないと思います。実際そういう言葉の使い方しているなら、別ですが、タスクはそれだけの概念でひとかたまりとして扱っているのではないでしょうか？あと、タスクをプロジェクトの集約の一部に入れた場合は、タスクが増えた時に性能上の問題がでそうです。

> これでは「プロジェクト集約」はグローバルなIDを持たないことになりますが、これは許されるのでしょうか？  
>  あるいは、「プロジェクト集約ルート」は集約ルートからただのエンティティに降格するのでしょうか。

プロジェクトはグローバルなIDを持つの集約ルートであり、グローバルなエンティティですね。たぶんですが…。

> そもそも集約ルートがグローバルなIDで管理されなければならない、というのがいまいちピンときていないのですが・・・

厳密にいうと集約ルートがグローバルなIDというより、グローバルなエンティティがというか話なんですが、DDD第二部のエンティティとリポジトリのところを読まれましたか？その辺に書いてます。実質、集約ルート＝グローバルエンンティティです。ドメインモデルを、ライフサイクルの側面でみるか、ユビキタス言語の側面でみるかの違いです。  
その、エンティティは特定が目的です。たとえば、人がエンティティだとしたら、名前や住所、年齢といった属性は時とともに変遷します。属性を使ってエンティティを特定するのは実質不可能です。そのためアイデンティティによって特定することになります。つまり識別子＝IDです(不変であることが前提)。だからIDが必要なんですよね。

> 上位の集約のID（上記例ではサービス契約者ID）にぶら下がるのでは、ダメなのでしょうか？

どちらも問題ありませんが、ユビキタス言語によりますね。

>
> それとも、DDD本にある「グローバルな同一性」とは、単一のIDを要求するものではなく、「サービス契約者ID＋プロジェクトID」という複合的なIDであっても、OKということでしょうか？

DDD本の集約の章(車とタイヤの例)にも書いてますが、車という集約にはタイヤという部品が集約されています。要件的に、タイヤの識別や特定が不要であれば、IDを持たないタイヤ値オブジェクトを車集約に構成させますが、識別が必要なら
タイヤをローカルエンティティにするでしょう。このタイヤローカルエンティティには、タイヤの取り付けポジション(左前、右前、左後、右後)をIDとするかもしれません。このIDは車内だけでユニークなIDです。グローバルな同一性か、そうじゃないかはそういう意味です。  
これとは別に複合的なコンポジットIDを使うかどうかは要件次第でDDDで規定する範囲ではないと思います。


CC BY-SA 3.0
2015-02-19T05:22:56.040
6732
2015-02-19T05:22:56.040
5610
6620
answer


エバンスのドメイン駆動設計で書いてある「グローバルな同一性」というのは、「グローバルなID」とは等価ではないと思いますよ。

ある実体（エンティティ）が同じものであることを識別する時に、オブジェクトのIDやデータベースの主キーの議論と、「人がどう判断するか」の議論はいったんは分けて考えたほうが良いです。

ドメイン駆動設計の文脈で言えば、「同一性」の議論で大切なのは、利用者が、どんな情報を判断材料として、同じプロジェクトと別のプロジェクトを認識するか、です。

あるサービス契約者（とのそのサービスの利用者）が、自分が見えるすべてのプロジェクトの範囲で、プロジェクト名で判断できるなら、同一性のキーとなる情報はプロジェクト名です。

サービスの運営者（とシステムの開発者）から見れば、理屈の上では、プロジェクトの識別は、サービス契約者＋プロジェクト名ということになりますが、実際に、そういう識別が必要になるケースは、業務的にはないんじゃないでしょうか？
全てのサービス契約者の全てのプロジェクト一覧、というような画面が必要かどうか、ということです。

おそらく、サービス契約者一覧は必要だが、プロジェクト一覧は、特定のサービス契約者ごとのプロジェクト一覧があれば十分かと思います。この場合、プロジェクトを表すオブジェクト自身が、サービス契約者まで含めて識別情報を持つ必要は、ないと思います。

もし、全てのサービス契約者にまたがってプロジェクトを一覧したい、というニーズがあるのなら、プロジェクトの同一性は、サービス契約者＋プロジェクトの組み合わせの設計になるかと思います。


CC BY-SA 3.0
2015-02-19T07:19:24.427
6746
2015-02-19T07:19:24.427
8260
6620
answer


エンティティの識別子は、複合IDでも良いと思います。

具体的には、以下のように ProjectId を値オブジェクトとして定義できます。(Python の場合)

[code]

    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
    
[/code]

ただ、 @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 をグローバルに一意とすることで、随分と設計がシンプルになりました。

ご参考になると幸いです。

### 追記

やや本論からは話がそれますが、「サービス契約者」を管理するコンテキスト（例：アカウント管理のコンテキスト）と、「プロジェクト」を管理するコンテキスト（タスク管理のコンテキスト）が異なる場合は、後者のコンテキストにおいては「サービス契約者」の概念をコンテキストに適したユビキタス言語で表現しなおして、値オブジェクトとして扱うと便利です。


CC BY-SA 3.0
2015-02-19T08:51:14.100
6762
2015-02-19T09:02:01.210
2015-02-19T09:02:01.210
5997
5997
6620
answer


集約の中のルート以外のエンティティもローカルな同一性は持ちますから、ルートの同一性と組み合わせれば、グローバルにも同一性を持っていると言える筈です(データモデル流に言えば複合主キーを持つ)。

ですから、問題は、グローバルな同一性があるかどうかではなく、集約の外部のエンティティがそのエンティティへの参照を保持して良いか、ということだろうと思います。

その答えが、（DDD本 p.127によれば）集約ルートについては YES、以外の集約内部エンティティについては NO
となるようです（個人的には、このような設計指針の妥当性には疑問を感じますが、それはそれとして）。

以上からすれば、ご指摘の例では、他のサービス契約者に属するプロジェクトへの参照を保持するニーズが無いならば（無いと思いますが）、サービス契約者を集約とし、プロジェクトをその内部エンティティとしても構いませんが、そうしなければならない、ということもありません。

DDD本(p.128)では、集約を設ける目的として、不変条件の強制、変更管理、データベースの共有（更新時の排他）の３つを挙げています。

これらの要件を満たすデータの括りとしてプロジェクトが適切であればプロジェクトを集約としてもよいし、タスクが適切であればタスクを集約としてもよいと思います（後者の場合、集約と云ってもタスクしか含まれませんが）。

私としては、上述の要件を満たす限り、集約は小さく設計した方が、色んな点でメリットがあると思います。


CC BY-SA 3.0
2015-02-21T14:21:47.290
6869
2015-02-21T14:21:47.290
8429
6620
answer