DDD集計ルートを見つける


10

みんなのお気に入りのゲームをプレイして、Aggregrate Rootを見つけましょう。正規のCustomer / Order / OrderLines / Product problemドメインを使用してみましょう。従来、Customer、order、productはARであり、OrderLineはOrderの下のエンティティです。この背後にあるロジックは、顧客、注文、および製品を識別する必要があるが、OrderLineは注文なしには存在しないということです。したがって、私たちの問題領域では、顧客は一度に1つの未配達注文しか持てないというビジネスルールがあります。

これにより、注文は顧客の集計ルートの下に移動しますか?そうだと思います。しかし、そうすることで、顧客のARがかなり大きくなり、後で同時実行の問題が発生しやすくなります。

または、顧客が特定の製品をその生涯に1回しか注文できないというビジネスルールがある場合はどうでしょうか。これは、顧客が注文を所有することを要求するより多くの証拠です。

しかし、配送に関しては、顧客ではなく注文に対してすべてのアクションを実行します。個々の注文を配送済みとしてマークするために顧客全体をロードする必要があるのは、一種の愚かです。

これは私が提案しているものです:

class Customer
{
    public Guid Id {get;set;}
    public string Name { get; set; }
    public Address Address { get; set; }
    public IEnumerable<Order> Orders { get; set; }
    public void PlaceOrder(ThingsInTheOrder thingsInTheOrder)
    {
        // Make sure there aren't any pending orders already.
        // Make sure they aren't ordering a Widget if they've already ordered a Widget in the past.
        // Create an Order object and add it to the collection.  Raise a domain event to trigger emails and other stuff
    }
}

class Order
{
    public Guid Id { get; set; }
    public IEnumerable<OrderLine> OrderLines { get; set; }
    public ShippingData {get;set;}
    public void Ship(ShippedByPerson shippedByPerson, string trackingCode)
    {
         // Create a new ShippingData object and assign it from the data passed in.  
         // Publish a domain event
    }
}

私の最大の懸念は、同時実行の問題と、注文自体が集計ルートの特性を持っているという事実です。

回答:


12

短縮版

DDDの理論的根拠は、ドメインオブジェクトが機能的なドメイン要件を満たす必要がある抽象化であることです。ドメインオブジェクトがそれらの要件を簡単に満たせない場合は、間違った抽象化を使用している可能性があります。

エンティティ名詞を使用してドメインオブジェクトに名前を付けると、それらのオブジェクトが互いに密結合し、肥大化した「神」オブジェクトになる可能性があります。また、「質問の正しい場所はどこにあるか」などの問題を投げかける可能性があります。 CreateOrderメソッド?」

「正しい」集約ルートを特定しやすくするには、ドメインオブジェクトが機能的な高レベルのビジネス要件に基づいている別のアプローチを検討してください。つまり、システムのユーザーが必要とする機能要件や動作を示唆する名詞を選択します。実行します。


ロングバージョン

DDDはOO設計へのアプローチであり、システムのビジネスレイヤードメインオブジェクトのグラフを表示することを目的としています。ドメインオブジェクトは、高レベルのビジネス要件を満たし、理想的にはデータレイヤーに依存できる必要があります。基礎となる永続データストアのパフォーマンスや整合性などのために。

それを見る別の方法は、このリストの箇条書きである可能性があります

  • エンティティ名詞は通常、データ属性を提案します。
  • ドメイン名詞は振る舞いを示唆すべき
  • DDDとOOモデリングは、機能要件とコアドメイン/ビジネスロジックに基づく抽象化に関係しています。
  • ビジネスロジックレイヤーは、高レベルのドメイン要件を満たす責任があります。

DDDに関する一般的な誤解の1つは、ドメインオブジェクトは物理的な現実世界の「もの」(つまり、あらゆる種類のデータ/プロパティに起因する、現実世界で指すことができる名詞)に基づく必要があるということですが、データこれらの現実世界の/属性は、機能要件を特定しようとするときに、必ずしも良い出発点とはなりません。

もちろん、ビジネスロジックはこのデータを使用する必要がありますが、ドメインオブジェクト自体は、最終的には機能的なドメインの要件と動作を表す抽象化である必要があります。

例えば; 以下のような名詞OrderやはCustomerどんな行動を意味し、したがって、一般的なビジネスロジックを表現するために役に立たない抽象化され、ドメインオブジェクトはありません。

ビジネスロジックの表現に役立つ可能性のある抽象化の種類を探すときは、システムが満たすことが期待される一般的な要件を考慮してください。

  • 販売担当者として、販売する製品の価格と数量を記載した請求書を作成できるように、新しい顧客の注文を作成します。
  • カスタマーサービスアドバイザーとして、保留中の注文をキャンセルして、倉庫オペレーターが注文を処理できないようにしたいと考えています。
  • カスタマーサービスアドバイザーとして、製品を在庫に調整し、顧客の元の支払い方法で支払いを返金できるように、注文明細を返却したいと考えています。
  • 倉庫オペレーターとして、保留中のすべての商品と配送情報を表示して、商品を選択してクーリエ経由で発送できるようにしたいと考えています。

DDDアプローチによるドメイン要件のモデリング

上記のリストに基づいて、そのような注文システムのいくつかの潜在的なドメインオブジェクトを検討してください。

SalesOrderCheckout
PendingOrdersStream
WarehouseOrderDespatcher
OrderRefundProcessor 

これらはドメインオブジェクトとして、さまざまな動作ドメイン要件の所有権を持つ抽象化を表します。実際、彼らの名詞は、彼らが果たす特定の機能要件を強く示唆しています。

EventMediator新しい注文が作成されたとき、または注文が出荷されたときなどを知りたいオブザーバーに通知を渡すなど、追加のインフラストラクチャもある可能性があります)。

たとえば、SalesOrderCheckoutおそらく顧客、配送、製品に関するデータを処理する必要がありますが、注文の配送、保留中の注文の並べ替え、または払い戻しの発行の動作には関係ありません。

SalesOrderCheckoutドメイン要件を満たすためには、顧客が多すぎるアイテムを注文することを防ぎ、おそらく検証を実行し、おそらくシステムの他の部分の通知を上げるなどのビジネスルールを適用することが含まれます。他のオブジェクトの。

エンティティ名詞を使用してドメインオブジェクトを表すDDD

このような単純な名詞治療の潜在的な危険がいくつかありOrderCustomerそしてProductドメインオブジェクトとしては、それらの問題の中には、あなたが質問で暗示するものがあります:

  • この方法は、処理する場合OrderCustomerおよびProductそれが属しているドメインオブジェクトを、?
  • これら3つのオブジェクトの集約ルートはどこにありますか?

ドメインオブジェクトを表すためにエンティティ名詞を選択すると、次のようなことが起こります。

  • OrderCustomerおよびProduct「神」のオブジェクトに成長するリスクがある
  • Managerすべてを結びつけるために単一の神オブジェクトで終わるリスク。
  • これらのオブジェクトは相互に密結合するリスクがあります- this(またはself)を渡さずにドメイン要件を満たすのは難しい場合があります
  • 「リークの多い」抽象化を開発するリスク-つまり、ドメインオブジェクトはカプセル化を弱める数十のget/ setメソッドを公​​開することが期待されています(そうでない場合、他のプログラマーが後で説明するでしょう)。
  • ビジネスデータ(UIを介したユーザーデータ入力など)と一時的な状態(注文が変更されたときのユーザーアクションの「履歴」など)の複雑な混合により、ドメインオブジェクトが肥大化するリスク。

DDD、OOデザイン、プレーンモデル

DDDとOOデザインに関する一般的な誤解は、「プレーン」モデルはどういうわけか「不良」または「アンチパターン」であるということです。マーティンファウラーは貧血ドメインモデルについての記事を書いていますが、彼がその記事で明らかにしているように、DDD自体はレイヤー間のクリーンな分離のアプローチに「矛盾」すべきではありません

「ドメインオブジェクトに動作を組み込むことは、永続化やプレゼンテーションの責任などからドメインロジックを分離するためにレイヤーを使用するという確固たるアプローチと矛盾しないことを強調する価値もあります。ドメインオブジェクトにあるべきロジックは、ドメインロジック-検証、計算です。 、ビジネスルール-あなたがそれをなんと呼んでもいい」

つまり、プレーンモデルを使用して他のレイヤー間で転送されるビジネスデータ(たとえば、ユーザーが新しい注文を作成したいときにユーザーアプリケーションから渡される注文モデル)を保持することは、「貧血ドメインモデル」と同じではありません。「プレーン」データモデルは、多くの場合、データを追跡し、レイヤー間でデータを転送するための最良の方法です(REST Webサービス、永続ストア、アプリケーションまたはUIなど)。

ビジネスロジックはこれらのモデルのデータを処理し、ビジネス状態の一部として追跡できますが、必ずしもこれらのモデルの所有権を取得するわけではありません。

集計ルート

ドメインは、オブジェクトの例を再び見ると- 、SalesOrderCheckout、、PendingOrdersStream 明らかな集約ルートはまだありません。しかし、これらのドメインオブジェクトには、重複していないように見える非常に個別の責任があるため、実際には問題になりません。 WarehouseOrderDespatcherOrderRefundProcessor

機能的には、データベースに新しい注文を追加したときに前者のジョブが完了SalesOrderCheckoutするPendingOrdersStreamので、がと対話する必要はありません。一方、PendingOrdersStreamはデータベースから新しい注文を取得できます。これらのオブジェクトは実際には相互に直接対話する必要はありません(おそらく、イベントメディエーターが2つのオブジェクト間で通知を提供する可能性がありますが、これらのオブジェクト間の結合は非常に緩やかであると予想します)。

おそらく、Aggregate Rootは、IoCコンテナになり、これらのドメインオブジェクトの1つ以上をUIコントローラに挿入し、EventMediatorやなどの他のインフラストラクチャも提供しRepositoryます。あるいは、ビジネス層の上にある一種の軽量のオーケストレータサービスになるかもしれません。

集約ルートは必ずしもドメインオブジェクトである必要はありません。ドメインオブジェクト間で懸念の分離を維持するために、集約ルートがビジネスロジックを持たない別のオブジェクトである場合は、一般的に良い方法です。


3
同じ名前のエリックエバンスが書いた本のドメインドリブンデザインを使用したMicrosoft固有のテクノロジーであるEntity Frameworkの概念をあなたの回答が統合するため、反対票を投じました。DDDブックと直接矛盾する回答がいくつかありますが、この質問ではEntity Frameworkについてまったく言及されていませんが、特にDDDでタグ付けされています。質問には永続性についての言及もまったくないので、データベーステーブルが関連する場所はわかりません。
RibaldEddie 2017

@RibaldEddie回答を確認してコメントしていただきありがとうございます。永続的なデータについての言及は回答に含まれる必要がないことに同意するので、これを削除するために言い換えました。回答の主な焦点は、「エンティティ名詞は密結合の肥大化した神オブジェクトになる傾向があるため、しばしばドメインオブジェクトクラス名としてあまり適切ではない」として要約できます。メッセージと理由付けWRT機能要件/動作がより明確になりました?
Ben Cottrell 2017

DDDの本にはその概念IIRCはありません。これにはエンティティがあります。これは単なるインスタンスであり、インスタンス化されたときに永続的で一意のIDを持つため、2つの個別のインスタンスは2つの一意で永続可能なものを意味します。 。本では、EntitiesオブジェクトとValueオブジェクトの両方がドメインオブジェクトです。
RibaldEddie 2017

10

集計を定義する基準は何ですか?

大きな青い本の基本に戻りましょう。

集合:データ変更の目的で1つの単位として扱われる関連オブジェクトのクラスター。外部参照は、ルートとして指定されたAGGREGATEの1つのメンバーに制限されます。一連の整合性ルールがAGGREGATEの境界内に適用されます。

目標は、不変条件を維持することです。しかし、それは適切にローカルなアイデンティティを管理することでもあります。

OrderそしてOrder line間違いなくそのようなクラスターに属しています。例えば:

  • を削除するとOrder、そのすべての行を削除する必要があります。
  • 行を削除すると、次の行の再番号付けが必要になる場合があります
  • 新しいラインを追加するには、同じ注文の他のすべてのラインに基づいてライン番号を決定する必要があります。
  • 通貨などの一部の注文情報を変更すると、ラインアイテムの価格の意味に影響する場合があります(または価格の再計算が必要になる場合があります)。

したがって、ここでは、整合性のルールと不変条件を保証するために、完全な集計が必要です。

いつ停止しますか?

次に、いくつかのビジネスルールについて説明し、それらを保証するために、顧客を集合体の一部と見なす必要があると主張します。

お客様には、一度に1つの注文しか配達できないというビジネスルールがあります。

もちろん、違います。その影響を見てみましょう。注文は常に顧客からアクセスされます。これは現実ですか?労働者が注文を配送するためのボックスに記入するとき、彼らは注文にアクセスするために顧客のバーコードと注文のバーコードを読み取る必要がありますか?実際、一般に、注文のIDは顧客に対してローカルではなくグローバルであり、この相対的な独立性により、注文の全体から外れることが示唆されています。

さらに、これらのビジネスルールはポリシーとしてよりよく見えます。これらのルールを使用してプロセスを実行するかどうかは、会社の任意の決定です。ルールが守られていない場合、上司は不満を抱くかもしれませんが、データは実際には矛盾していません。さらに、夜通しの「顧客ごとに一度に1件の未配達注文」は「顧客ごとに10件の未配達注文」、または「顧客から独立して、倉庫ごとに100件の未配達注文」になる可能性があるため、集計が正当化されなくなる可能性があります。


1

私たちの問題のドメインでは、お客様は一度に1つの注文しか配達できないというビジネスルールがあります。

そのうさぎの穴を深く掘り下げる前に、セットの一貫性に関するGreg Youngの議論、特に次のことを確認する必要があります。

失敗した場合のビジネスへの影響は何ですか?

多くの場合、正しい答えは、間違ったことが起こらないようにすることではなく、問題が発生したときに例外レポート生成することです。

しかし、複数の未配達の注文があなたのビジネスにとって重大な責任であると仮定します...

はい、未配達の注文が1つだけであることを確認する場合は、顧客のすべての注文を表示できる集計が必要です。

その集計は必ずしも顧客の集計ではありません

特定の顧客のすべての注文が同じキューに入れられる注文キューまたは注文履歴のようなものです。あなたが言ったことから、それは顧客のプロファイルデータのすべてを必要としないので、それはこの集計の一部であってはなりません。

しかし、配送に関しては、顧客ではなく注文に対してすべてのアクションを実行します。

はい、実際にフルフィルメントとプルシートで作業している場合、履歴ビューは特に関係ありません。

履歴ビューでは、不変条件を適用するために、注文IDとその現在の処理ステータスのみが必要です。これは必ずしも順序と同じ集計の一部である必要はありません。集計の境界は、ビューの構造化ではなく、変更の管理に関するものです。

したがって、注文を集約として処理し、注文履歴を別個の集約として処理して、2つの間のアクティビティを調整することができます。


1

わらの人の例を設定しました。これは単純すぎるため、現実のシステムを反映しているとは思えません。そのため、これらのエンティティとそれに関連する動作を、指定した方法でモデル化することはしません。

クラスは、複数の集計に反映される方法で注文の状態をモデル化する必要があります。例えば、顧客の注文要求を処理する必要がある状態に顧客プットシステムが、私はと呼ばれるドメインエンティティオブジェクトの集約を作成する可能性がある場合CustomerOrderRequestPendingCustomerOrder、あるいは単にCustomerOrder、または任意の言語のビジネス用途を、そしてそれが両方へのポインタを保持することができCustomerとOrderLinesにcanCustomerCompleteOrder()、サービスレイヤーから呼び出されるようなメソッドがあります。

このドメインオブジェクトには、注文が有効かどうかを判断するビジネスロジックが含まれます。

注文が有効で処理されていれば、このオブジェクトを、処理された注文を表す別のオブジェクトに移行する方法がいくつかあります。

あなたの理解の問題は、あなたが集計の単純化された過度に単純化された例を使用していることだと思います。A PendingOrderUndeliveredOrder、aから分離された独自の集合体でDeliveredOrderあるCancelledOrder場合もあり、a またはa から分離された別の集合体である場合もあります。


ジェンダーに中立な言葉でのあなたの試みは面白いですが、私は女性がカラスを怖がらせるために畑に立つことは決してないことに注意します。
ロバートハーベイ

@RobertHarveyそれは私の投稿で注目するのは奇妙なことです。かかしとエフィミーは両方とも、歴史を通して定期的に女性の形で現れました。
RibaldEddie 2017

あなたがそれを重要だと考えなければ、あなたはあなたの投稿で区別をしなかったでしょう。言語学の問題として、この用語は「ストローマン」です。性差別に関する留保はほぼ間違いなく、あなた自身の用語を発明することによって生み出された「地獄で何が彼が話しているのか」という要素によって打ち負かされています。
ロバートハーベイ

5
@RobertHarvey誰かがわらの男の意味を知っているなら、彼らがその用語を聞いていなければ、わらの男が何を意味するのか理解できると私は確信しています。ソフトウェアについて投稿の内容に集中できますか?
RibaldEddie 2017

1

Vaughn Vernonは、第7章(サービス)の冒頭にある彼の著書「Implementing Domain-Driven Design」でこれについて言及しています。

「多くの場合、ドメインモデルでサービスを作成する必要があることを示す最も良い兆候は、実行する必要のある操作が、集約または値オブジェクトのメソッドとして場違いだと感じたときです。」

したがって、この場合、Customerインスタンスと注文のアイテムのリストを取得する「CreateOrderService」というドメインサービスが存在する可能性があります。

class CreateOrderService
{
    public Order CreateOrder(Customer customer, ThingsInTheOrder thingsInTheOrder)
    {
        // Get all the orders for the customer
        // Check if any of the things to be ordered exist in previous orders   
        // If none have been previously ordered, create the order and return it
        // Otherwise return null 
    }
}

1
ドメインサービスが問題の同時実行の問題に対処するのにどのように役立つかについて詳しく説明していただけますか?
ivenxu
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.