web-dev-qa-db-ja.com

トランザクションの完了後にドメインイベントを発生させる

関連する作業単位が正常にコミットしたときにのみイベントを発生させるドメインイベントシステムを実装しようとしています。

これを行う主な理由は、イベントを処理するときにこれらの変更が適用されることを期待するデータベースを読み取る他のサブシステムがあるためです。

また、コミットが失敗した場合に、イベントハンドラーが自分の作業(メールの送信など)を実行しないようにします。

システムは、複数のASP.NETアプリケーション(WebForms、MVC、およびWebAPI)です。

_public class Order
{
    private Payment _payment;

    public int ID { get; private set; }

    public decimal Amount { get; private set; }

    public void PayUsing(IPaymentProcessor processor)
    {
        if (_payment != null)
        {
            throw new InvalidOperationException(
                "You can't pay for an order twice");
        }

        // the Process method may also raise domain events
        _payment = processor.Process(Amount);

        // Raise saves the event into a static Queue<T>
        DomainEvents.Raise(new OrderPaidEvent(this));
    }
}

public class OrderFulfillmentService
{
    private readonly IUnitOfWorkFactory _unitOfWorkFactory;
    private readonly IPaymentProcessor _paymentProcessor;

    public OrderFulfillmentService(
        IUnitOfWorkFactory unitOfWorkFactory, 
        IPaymentProcessor paymentProcessor)
    {
        _unitOfWorkFactory = unitOfWorkFactory;
        _paymentProcessor = paymentProcessor;
    }

    public void Fulfill(int orderId)
    {
        using (var unitOfWork = _unitOfWorkFactory.Create())
        {
            var order = unitOfWork.OrdersRepository.GetById(orderId);

            order.PayUsing(_paymentProcessor);

            unitOfWork.Commit();

            // I only want the events to be raised if the Commit succeeds               
        }
    }
}
_

現在の実装では問題が発生します。現在、ドメインレイジングは、すぐに起動してはならないため、それをキューに入れる必要があるためです。キューは静的であり、マルチユーザーシステムに問題を引き起こします。すべてのユーザーが同じキューにアクセスするという明確な問題である、別のユーザーに対して誤ってイベントを発生させたくありません。

これは私が調査したソリューションのリストであり、それらが私のシナリオで機能しない理由です

  1. DomainRaiserメソッドに渡される非静的PayUsingクラスを用意します。

    • DomainRaiserを渡すことはドメインオブジェクトから行うのが非常に困難です。プロパティを変更してこのオブジェクトを渡すのが面倒な場合、イベントが発生することがあります。
  2. http://lostechies.com/jimmybogard/2014/05/13/a-better-domain-events-patternで説明されているように、_UnitOfWork.Commit_呼び出しによって読み取られるEvents列挙をすべてのエンティティに含める/

    • 変更された可能性のあるすべてのエンティティを追跡することは非常に困難です。この変更追跡をORMにオフロードしています。これにより、ドメインオブジェクトが乱雑になります。
  3. _HttpContext.Current.Items_を使用してユーザー固有のイベントを格納する

    • これは現在最良の提案ですが、単体テストを行うことはできません。また、将来的にデスクトップアプリをリリースする予定のasp.netを使用するようにドメインをロックします。

私の質問は、作業単位全体が成功した場合にのみイベントを発生させることを考慮しながら、これらのイベントをマルチユーザー環境で確実にキューに入れてディスパッチするにはどうすればよいですか?

5
Matthew

まず、各エンティティがDomainRaiserを参照していることを確認します。これは、エンティティーがリポジトリーで作成または実体化されるときに設定された場合に最適です。各ユーザー/リクエストコンテキストには独自のインスタンスがあり、そのインスタンスがすべてのエンティティに挿入され、コンテキストによって機能します。使用しているRepository/UnitOfWork実装がわからないので、不可能かもしれません。しかし、構築中に呼び出すことができるインターフェースをエンティティに与えることができれば、それは自動化されていると思います。

これは、UnitOfWorkが成功した場合にのみイベントを実行するという2番目の問題も解決します。 DomainRaiserUnitOfWorkが1つのコンテキストに単一のインスタンスを持っている場合、これらは相互に対話することができ、これによりこの単純なメソッド呼び出しが行われます。

最後に思い浮かぶのは、いくつかのイベントをprepareexecuteの2つの部分に分けることができるということです。イベントの発生元と同じUnitOfWorkでPrepareが呼び出されます。これにより、コミットを待たずに、イベントに関連するデータを読み取ることができます。次に、UnitOfWorkが正常にコミットされ、準備されたデータを使用する場合、実行はフラグ付きで外部で呼び出されます。

3
Euphoric