6

私は、貧血ドメインモデルを避けることについてどれだけ心配すべきかに関するいくつかのアドバイスを探しています。私たちはDDDを始めるばかりであり、単純な設計上の決定に関する分析の麻痺に苦しんでいます。私たちが固執している最新のポイントは、特定のビジネスロジックが属している場所です。たとえば、Statusなどのプロパティを持つOrderオブジェクトがあります。誰かが注文を間違えたため、UndoLastStatusのようなコマンドを実行する必要があります。 Statusを変更するだけでは簡単ではなく、他の情報を記録してプロパティを変更する必要があります。現実の世界では、これは純粋な管理作業です。ドメイン駆動型デザイン:貧血ドメインの回避と実際の世界の役割のモデリング

  • オプション1:このちょっと作る感覚ながら、それは本当にドメインを反映していない、Order.UndoLastStatus()のようなので、何かを注文するメソッドを追加しますので、私はそれを参照してください方法は、私は私が考えることができる2つのオプションがあります。また、Orderがシステム内の主要なオブジェクトであり、その注文に関係するすべてが注文クラスに置かれていれば、物事が損なわれる可能性があります。

  • オプション2:Shopオブジェクトを作成します。これには異なる役割を表す異なるサービスがあります。だから私はShop.AdminServiceShop.DispatchService、そしてShop.InventoryServiceを持っているかもしれません。したがってこの場合、私はShop.AdminService.UndoLastStatus(Order)を持っています。

は今、2番目のオプションは、我々はより多くのドメインを反映している何かを持っている、と開発者が実際に存在する同様の役割について、ビジネスの専門家に話をすることが可能になります。しかし、これも貧血モデルに向かっている。一般的にはどちらが良い方法でしょうか?

答えて

6

オプション2は、手順コードを確実に導きます。
開発は簡単かもしれませんが、維持するのがずっと難しくなります。現実の世界では今

これは

「管理」タスクは、プライベートとパブリック、完全に「domain`ish」アクションを介して起動する必要があり、純粋な管理タスクです。好ましくは、まだドメインから推し進められる分かりやすいコードで書かれています。

私が見ているように、問題はUndoLastStatusがドメインエキスパートにはほとんど意味がないということです。
彼らは注文の作成、キャンセル、記入について話している可能性が高いです。これらの線に沿って

何かが良く合うかもしれません:

class Order{ 
    void CancelOrder(){ 
    Status=Status.Canceled; 
    } 
    void FillOrder(){ 
    if(Status==Status.Canceled) 
     throw Exception(); 
    Status=Status.Filled; 
    } 
    static void Make(){ 
    return new Order(); 
    } 
    void Order(){ 
    Status=Status.Pending; 
    } 
} 

私は個人的に「ステータス」の使用を嫌うが、それらは自動的にそれらを使用するすべてのものに共有されている - 私はそのunnecessary couplingとしてを参照してください。注文の状態変化が、最善の策はそうdomain eventsと呼ばれる使用するとき

class Order{ 
    void CancelOrder(){ 
    IsCanceled=true; 
    } 
    void FillOrder(){ 
    if(IsCanceled) throw Exception(); 
    IsFilled=true; 
    } 
    static Order Make(){ 
    return new Order(); 
    } 
    void Order(){ 
    IsPending=true; 
    } 
} 

関連のものを変更する場合:

だから私はこのようなものを持っているでしょう。
私のコードは、これらの線に沿ってになります。

class Order{ 
    void CancelOrder(){ 
    IsCanceled=true; 
    Raise(new Canceled(this)); 
    } 
    //usage of nested classes for events is my homemade convention 
    class Canceled:Event<Order>{ 
    void Canceled(Order order):base(order){} 
    }  
} 

class Customer{ 
    private void BeHappy(){ 
    Console.WriteLine("hooraay!"); 
    } 
    //nb: nested class can see privates of Customer 
    class OnOrderCanceled:IEventHandler<Order.Canceled>{ 
    void Handle(Order.Canceled e){ 
    //caveat: this approach needs order->customer association 
    var order=e.Source; 
    order.Customer.BeHappy(); 
    } 
    } 
} 

注文があまりにも巨大に成長した場合、あなたはエリック・エヴァンスが言うようbounded contextsは(何であるかをチェックすることをお勧めします - 彼は彼の本を書いた機会があった場合再び、彼は有界の文脈を始めに移すだろう)。

要するに、それはドメインによって引き起こされる分解の形式です。

アイデアは比較的単純です。さまざまな観点から別のコンテキストを持つ複数のオーダーを持つことは問題ありません。

など。 - ショッピングコンテキストからの注文、会計コンテキストからの注文。

namespace Shopping{ 
class Order{ 
    //association with shopping cart 
    //might be vital for shopping but completely irrelevant for accounting 
    ShoppingCart Cart; 
} 
} 
namespace Accounting{ 
class Order{ 
    //something specific only to accounting 
} 
} 

しかし、通常、十分なドメイン自体は複雑さを避け、十分に耳を傾ければ簡単に分解することができます。例えば。 OrderLifeCycle、OrderHistory、OrderDescriptionなどのエキスパート用語から、分解のためのアンカーとして活用できることが分かるかもしれません。

注意:あなたのドメインについての理解は全くありません。
私が使用している動詞は完全に奇妙なものです。

+0

遅い返答をおかけして申し訳ありません、このような詳細でよく考えられた応答を提供していただきありがとうございます。おそらく誰かが私に提供してくれたより良い答えの1つです。 –

+0

お客様が注文をキャンセルした場合、次のビジネスルールがあります:1)カートから注文を取り除く、2)店舗で予約を取り除く、3)顧客のお金を補償する(既に支払い済みの場合)?それらが 'Order.Cancel'メソッドにあるとは想像できません。 – Lightman

0

私はGRASPの原則に従います。 インフォメーションエキスパートデザイン原則を適用します。つまり、変更を実行するために必要な情報が当然あるクラスに責任を割り当てる必要があります。

この場合、注文状況の変更には他のエンティティが含まれているため、これらの低レベルのドメインオブジェクトのそれぞれに、自分自身に関する変更を適用する方法をサポートするようにします。次に、必要に応じて複数のドメインオブジェクトにまたがる操作全体を抽象化するオプション2で説明したように、ドメインサービスレイヤーを使用します。

また、Facadeパターンを参照してください。

+0

注文ステータスを変更すると、注文集約内のエンティティのみが関与します。だから本質的に私は両方のオプションを適用し、Shop.AdminService.UndoLastStatus(Order)は単にOrder.UndoLastStatus()を呼び出すだろうか? –

+0

私はあなたの質問から、注文を元に戻すときにやるべき他の必然的な作業があることを理解しました。そのため、タスクは1つのレベルに属するべきだと思ったので、ロガーなどの他のオブジェクトにアクセスできます。 –

0

OrderクラスのUndoLastStatusのようなメソッドを持つのは、その存在理由がオーダーのスコープ外の意味であるため、少し間違っているように思えます。一方、Order.ChangeStatusという注文のステータスを変更する責任を負うメソッドを持つことは、ドメインモデルとしてうまく適合します。オーダーのステータスは適切なドメインのコンセプトであり、オーダー・ステータスに関連するデータを所有しているため、そのステータスの変更はオーダー・クラスを介して行う必要があります。オーダー・クラスは、オーダー・クラス自体が一貫性を保ち、適切な状態に保つ責任があります。それを考えるために

もう一つの方法は、Orderオブジェクトをデータベースに永続何であり、それは注文に適用されたすべての変更のための「最後の停止」であるということです。注文の有効な状態が、外部コンポーネントの観点からではなく、オーダーの観点からのものである可能性があることを推論する方が簡単です。これは、DDDとOOPがまさに約束していることであり、人間がコードを推論するのが容易になります。さらに、状態の変更を実行するには、プライベートメンバーまたは保護されたメンバーへのアクセスが必要な場合があります。その場合、メソッドがオーダークラスにある方が良いオプションです。これは、貧血ドメインモデルがぶつかる理由の1つです。つまり、状態を一貫して所有クラスから守る責任を移し、それによって他のものとの間でカプセル化を破ります。

UndoLastStatusなどのより具体的な操作を実装する方法の1つは、ドメインを公開するOrderServiceを作成することです。これは外部コンポーネントがドメインでどのように動作するかを示します。 OrderServiceのは、そのコマンドを処理する方法を持っているでしょう

class UndoLastStatusCommand { 
    public Guid OrderId { get; set; } 
} 

アン:次に、あなたは、このような単純なコマンドオブジェクトを作成することができます

public void Process(UndoLastStatusCommand command) { 
    using (var unitOfWork = UowManager.Start()) { 
    var order = this.orderRepository.Get(command.OrderId); 
    if (order == null) 
     throw some exception 

    // operate on domain to undo last status 

    unitOfWork.Commit(); 
    } 
} 

をだから今ご注文のためのドメインモデルは、すべてのデータを公開し、 Orderに対応する動作であるが、OrderServiceおよび一般的なサービス層は、注文に対して実行される異なる種類のオペレーションを宣言し、プレゼンテーション層などの外部コンポーネントによる利用のためにドメインを公開する。

また、貧血ドメインモデルとその改善方法を考慮したdomain eventsという概念を検討することを検討してください。

+0

なぜUndoLastStatusCommandを作成したのですか?OrderIdに直接渡すのはなぜですか? –

+0

コマンドオブジェクトは、メッセージ指向のサービスレイヤで特殊なタイプのメッセージとして機能します。これは、サービスレイヤを構成する単なる1つの方法であり、CQRSなどの非同期統合パターンに基づいています。したがって、注文IDをUndoLastStatusというサービスメソッドに直接渡すことも有効ですが、最終的にはサービスレイヤに設定された制約と要件に依存します。 – eulerfx

0

テストからこのドメインを動かさないようです。 Rob Vensの仕事、特に探索的モデリング、時間反転、アクティブ - パッシブに関する彼の作業を見てみましょう。

関連する問題