2

私はドメイン集約を持っており、それを「オーダー」と呼び、オーダーラインのリストを含んでいます。注文は、注文明細の金額の合計を追跡します。顧客は、データベース トランザクションの履歴を合計することによって計算された、注文可能な実行中の「クレジット」残高を持っています。「プール」のお金をすべて使い果たすと、それ以上製品を注文できなくなります。

そのため、注文に行が追加されるたびに、プールに残っている金額と、注文がそれを超えているかどうかを確認する必要があります。プール内の金額は、他の関連する顧客が継続的に使用しているため、継続的に変化しています。

問題は、DDD の観点から考えると、ドメイン層を DataContext の問題で汚染したくないので (ここでは L2S を使用して)、その量をどのように取得するかです。ドメインからデータベースにクエリを実行することはできないので、ビジネス ルールを検証できるようにするにはどうすればそのデータを取得できますか?

これはドメイン イベントが使用されるインスタンスですか?

4

3 に答える 3

5

Order 集計は完全にカプセル化する必要があります。したがって、アイテムを追加することが有効かどうか、つまり、顧客のクレジットを超えているかどうかを判断できる必要があります。これを行うにはさまざまな方法がありますが、それらはすべて、この特定のことを行う方法を知っている特定の集計を返す Order リポジトリに依存しています。これはおそらく、たとえば、注文を満たすために使用するものとは異なる注文集計になります。

この場合、注文が特定の役割、つまり追加の品目を追加する役割を果たすことを期待しているという事実を認識し、コードに取り込む必要があります。これを行うには、このロールのインターフェイスと、ロールの内部サポートを持つ対応する集約を作成します。

次に、サービス レイヤーは Order リポジトリに、この明示的な役割インターフェイスを満たす注文を要求できます。したがって、リポジトリには、その要件を満たすものを構築できるようにするために必要なものに関する十分な情報があります。

例えば:

public interface IOrder
{
  IList<LineItem> LineItems { get; }
  // ... other core order "stuff"
}

public interface IAddItemsToOrder: IOrder
{
  void AddItem( LineItem item );
}

public interface IOrderRepository
{
  T Get<T>( int orderId ) where T: IOrder;
}

これで、サービス コードは次のようになります。

public class CartService
{
  public void AddItemToOrder( int orderId, LineItem item )
  {
    var order = orderRepository.Get<IAddItemsToOrder>( orderId );
    order.AddItem( item );
  }
}

次に、実装する Order クラスにIAddItemsToOrderは、クレジット残高を確認できるように顧客エンティティが必要です。したがって、特定のインターフェイスを定義することで、同じ手法をカスケードするだけです。注文リポジトリは、顧客リポジトリを呼び出して、その役割を果たす顧客エンティティを返し、それを注文集計に追加できます。

したがって、基本ICustomerインターフェースがあり、それから派生するインターフェースの形式で明示的な役割がありICustomerCreditBalanceます。はICustomerCreditBalance、Customer リポジトリへのマーカー インターフェイスとして機能し、顧客が何のために必要かを伝えるため、適切な Customer エンティティを作成できます。また、特定のロールをサポートするためのメソッドやプロパティが含まれています。何かのようなもの:

public interface ICustomer
{
  string Name { get; }
  // core customer stuff
}

public interface ICustomerCreditBalance: ICustomer
{
  public decimal CreditBalance { get; }
}

public interface ICustomerRepository
{
  T Get<T>( int customerId ) where T: ICustomer;
}

明示的なロール インターフェイスは、データベースからどのデータをフェッチするか、およびそれを積極的にフェッチするか遅延的にフェッチするかについて正しい決定を下すために必要な重要な情報をリポジトリに提供します。

この場合、インターフェイスにCreditBalanceプロパティを配置したことに注意してください。ICustomerCreditBalanceただし、基本ICustomerインターフェース上にありICustomerCreditBalance、空の「マーカー」インターフェースになって、クレジット残高を照会することをリポジトリに知らせることもできます。返されるエンティティに必要なロールをリポジトリに知らせることがすべてです。

質問で述べたように、これをすべてまとめる最後の部分はドメイン イベントです。注文は、顧客のクレジット残高を超える場合に障害ドメイン イベントを発生させ、注文が無効であることをサービス層に通知できます。一方、顧客に十分な信用がある場合は、顧客オブジェクトの残高を更新するか、ドメイン イベントを発生させて、残高を減らす必要があることをシステムの残りの部分に通知できます。

CartServiceこの回答はすでにかなり長いため、ドメイン イベント コードをクラスに追加していません。その方法について詳しく知りたい場合は、その特定の問題を対象とした別の質問を投稿することをお勧めします。そこで詳しく説明します;-)

于 2009-09-06T00:02:45.777 に答える
2

In such a scenario, I off-load responsibility using events or delegates. Maybe the easiest way to show you is with some code.

Your Order class will have a Predicate<T> that is used to determine if the customer's credit line is big enough to handle the order line.

public class Order
{
    public Predicate<decimal> CanAddOrderLine;

    // more Order class stuff here...

    public void AddOrderLine(OrderLine orderLine)
    {
        if (CanAddOrderLine(orderLine.Amount))
        {
            OrderLines.Add(orderLine);
            Console.WriteLine("Added {0}", orderLine.Amount);
        }
        else
        {
            Console.WriteLine(
                "Cannot add order.  Customer credit line too small.");
        }
    }
}

You will probably have a CustomerService class or something like that to pull the available credit line. You set the CanAddOrderLine predicate before adding any order lines. This will perform a check of the customer's credit each time a line is added.

// App code.
var customerService = new CustomerService();
var customer = new Customer();
var order = new Order();
order.CanAddOrderLine = 
    amount => customerService.GetAvailableCredit(customer) >= amount;

order.AddOrderLine(new OrderLine { Amount = 5m });
customerService.DecrementCredit(5m);

No doubt your real scenario will be more complicated than this. You may also want to check out the Func<T> delegate. A delegate or event could be useful for decrementing the credit amount after the order line is placed or firing some functionality if the customer goes over their credit limit in the order.

Good luck!

于 2009-08-07T12:47:53.863 に答える
1

「プール」値を取得する問題 (OrderRepository のメソッドを使用して値を照会する場所) に加えて、この問題のロックへの影響を考慮しましたか?

「プール」が常に変化している場合、ルールが通過した直後、変更をデータベースにコミットする直前に、他の誰かのトランザクションが忍び寄る可能性はありますか?

Eric Evans は、彼の著書 ("Aggregates") の第 6 章でまさにこの問題について言及しています。

于 2009-08-11T20:40:45.700 に答える