6

貧血ドメイン モデルを回避するためにどの程度注意を払う必要があるかについて、アドバイスを求めています。私たちは DDD を始めたばかりで、単純な設計上の決定に関する分析麻痺に苦しんでいます。私たちがこだわっている最新のポイントは、特定のビジネス ロジックが属する場所です。たとえば、Orderオブジェクトなどのプロパティがあります。たとえば、誰かが注文を間違えたために次のStatusようなコマンドを実行する必要があるとします。これはそれほど単純ではありません。UndoLastStatusを変更するだけでStatus、他の情報をログに記録し、プロパティを変更する必要があります。現実の世界では、これは純粋な管理タスクです。したがって、私が考える方法には、考えられる2つのオプションがあります。

  • オプション 1: メソッドを order に追加して、 のようOrder.UndoLastStatus()にします。これは理にかなっていますが、実際にはドメインを反映していません。またOrder、システムの主要なオブジェクトであり、注文に関連するすべてが注文クラスに配置されると、手に負えなくなる可能性があります。

  • オプション 2:Shopオブジェクトを作成し、さまざまな役割を表すさまざまなサービスを使用します。だから私は、、、およびを持っているかもしれShop.AdminServiceませShop.DispatchServiceShop.InventoryService。したがって、この場合、私はShop.AdminService.UndoLastStatus(Order).

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

4

4 に答える 4

6

オプション 2 は、確かに手続き型コードにつながります。
開発は簡単かもしれませんが、維持するのははるかに困難です。

現実の世界では、これは純粋な管理タスクです

「管理」タスクはプライベートであり、完全に「ドメインっぽい」パブリックなアクションによって呼び出される必要があります。できれば - ドメインから駆動される理解しやすいコードで書かれています。

私が見ているように、問題は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;
  }
}

私は個人的に「ステータス」の使用が嫌いです。それらは、それらを使用するすべてのものに自動的に共有されます-不要な結合と見なします.

したがって、次のようなものがあります。

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();
   }
  }
}

Order が大きくなりすぎた場合は、境界付けられたコンテキストとは何かを確認することをお勧めします (Eric Evans が言うように、彼が再び本を書く機会があれば、境界付けられたコンテキストを最初に移動するでしょう)。

要するに、これはドメインによって駆動される分解の形式です。

アイデアは比較的単純です。つまり、異なる視点 (コンテキスト) からの複数の Order を持つことは問題ありません。

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

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 などの専門用語を聞くかもしれません。

注意: 覚えておいてください - 私はあなたのドメインについてまったく理解していません.
私が使っているこれらの動詞は、完全に奇妙なものである可能性が非常に高いです。

于 2011-07-27T09:10:21.250 に答える
0

OrderクラスにUndoLastStatusのようなメソッドがあると、その存在の理由はある意味で注文の範囲外であるため、少し間違っていると思います。一方、注文のステータスの変更を担当するメソッドOrder.ChangeStatusを使用すると、ドメインモデルとして適切に適合します。注文のステータスは適切なドメインの概念であり、そのステータスの変更は、注文ステータスに関連付けられたデータを所有しているため、Orderクラスを介して行う必要があります-一貫性と適切な状態を維持するのはOrderクラスの責任です。

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

UndoLastStatusなどのより具体的な操作を実装する1つの方法は、ドメインを公開するOrderServiceを作成することであり、外部コンポーネントがドメイン上でどのように動作するかです。次に、次のような単純なコマンドオブジェクトを作成できます。

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

OrderServiceには、そのコマンドを処理するメソッドがあります。

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のドメインモデルはOrderに対応するすべてのデータと動作を公開しますが、OrderServiceとサービス層は一般に、注文で実行されるさまざまな種類の操作を宣言し、ドメインを公開して利用できるようにします。プレゼンテーション層などの外部コンポーネント。

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

于 2011-07-27T03:02:47.227 に答える
0

テストからこのドメインを駆動していないようです。Rob Vensの作品、特に探索的モデリング、時間反転、アクティブ-パッシブに関する彼の作品をご覧ください。

于 2012-03-12T22:10:51.303 に答える
0

私はGRASPの原則に導かれるでしょう。Information Expertの設計原則を適用します。つまり、変更を実行するために必要な情報が最も多いクラスに責任を割り当てる必要があります。

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

Facadeパターンも参照してください。

于 2011-07-26T01:03:53.717 に答える