35

ASP.NET MVC3 / NHibernate アプリケーションには、ドメイン オブジェクトに関連するさまざまなイベントを起動して処理する必要があります。たとえば、OrderオブジェクトにはOrderStatusChangedやなどのイベントが含まれる場合がありますNoteCreatedForOrder。ほとんどの場合、これらのイベントによってメールが送信されるため、MVC アプリにそのままにしておくことはできません。

私は、Udi Dahan のDomain Eventsや、この種のことを行う方法についての他の多くの考えを読み、イベント メッセージを処理する NServiceBus ベースのホストを使用することにしました。私はいくつかの概念実証テストを行いましたが、これはうまくいくようです。

私の質問は、どのアプリケーション層が実際にイベントを発生させるべきかということです。問題のオブジェクトが正常に永続化されるまでイベントを発生させたくありません (永続化が失敗した場合、メモが作成されたという電子メールを送信できません)。

もう 1 つの懸念事項は、場合によっては、イベントが集約ルートの下にあるオブジェクトに関連付けられることです。上記の例では、コレクションにNote追加して順序を保存することで a が保存されます。これは、が保存されOrder.Notesたときにどのイベントを起動する必要があるかを評価するのが難しくなるという問題を引き起こします。Order更新されたコピーを保存する前に、オブジェクトの現在のコピーを取得して違いを探す必要はありません。

  • UI がこれらのイベントを発生させるのは適切ですか? どのイベントが発生したかを認識し、サービス層がオブジェクトを正常に保存した後でのみ、イベントを起動できます。コントローラがドメイン イベントを発生させると、何かがおかしいように見えます。

  • 正常に永続化された後、リポジトリはイベントを発生させる必要がありますか?

  • イベントを完全に分離し、リポジトリにオブジェクトを格納し、それEventをポーラー サービスによって取得して、NServiceBus のイベントに変換する (またはポーラー サービスから直接処理する) 必要がありますか?

  • これを行うより良い方法はありますか?オブジェクトが永続化された後にのみ、サービス層によって起動されるイベントをドメイン オブジェクトでキューに登録することはできますか?

  • 更新:私はサービス層を持っていますが、特定の集約ルートが保存されたときにどのイベントを起動する必要があるかを決定するために、比較プロセスを実行するのは面倒で過剰に思えます。これらのイベントの一部は詳細 (「注文ステータスが変更されました」など) であるため、オブジェクトの DB コピーを取得し、プロパティを比較してイベントを作成し、新しいオブジェクトを保存してから、イベントが発生したときに NServiceBus に送信する必要があると思います。保存操作が正常に完了しました。

アップデート

以下に投稿した回答(以下の方法)に続いて、私が最終的に行ったことは、ドメインエンティティEventQueueList<IDomainEvent>. 次に、ドメインへの変更に値するイベントを追加しました。これにより、ロジックをドメイン内に保持することができました。これは、エンティティ内で何が起こっているかに基づいてイベントを発生させるため、適切であると考えています。

次に、サービス レイヤーでオブジェクトを永続化するときに、そのキューを処理し、実際にイベントをサービス バスに送信します。最初は、ID PK を使用するレガシー データベースを使用することを計画していたので、これらのイベントを後処理してエンティティの ID を入力する必要がありましたが、最終的には、Guid.Combその手順をスキップできる PK に切り替えることにしました。

4

6 に答える 6

10

私の解決策は、ドメイン層とサービス層の両方でイベントを発生させることです。

あなたのドメイン:

public class Order
{
    public void ChangeStatus(OrderStatus status)
    {
        // change status
        this.Status = status;
        DomainEvent.Raise(new OrderStatusChanged { OrderId = Id, Status = status });
    }

    public void AddNote(string note)
    {
        // add note
        this.Notes.Add(note)
        DomainEvent.Raise(new NoteCreatedForOrder { OrderId = Id, Note = note });
    }
}

あなたのサービス:

public class OrderService
{
    public void SubmitOrder(int orderId, OrderStatus status, string note)
    {
        OrderStatusChanged orderStatusChanged = null;
        NoteCreatedForOrder noteCreatedForOrder = null;

        DomainEvent.Register<OrderStatusChanged>(x => orderStatusChanged = x);
        DomainEvent.Register<NoteCreatedForOrder>(x => noteCreatedForOrder = x);

        using (var uow = UnitOfWork.Start())
        {
            var order = orderRepository.Load(orderId);
            order.ChangeStatus(status);
            order.AddNote(note);
            uow.Commit(); // commit to persist order
        }

        if (orderStatusChanged != null)
        {
            // something like this
            serviceBus.Publish(orderStatusChanged);
        }

        if (noteCreatedForOrder!= null)
        {
            // something like this
            serviceBus.Publish(noteCreatedForOrder);
        }
    }
}
于 2011-12-12T04:50:53.453 に答える
7

ドメイン イベントは、ドメイン内で発生する必要があります。それがドメイン イベントである理由です。

public void ExecuteCommand(MakeCustomerGoldCommand command)
{
    if (ValidateCommand(command) == ValidationResult.OK)
    {
        Customer item = CustomerRepository.GetById(command.CustomerId);
        item.Status = CustomerStatus.Gold;
        CustomerRepository.Update(item);
    }
}

(そして Customer クラスでは、次のようになります):

public CustomerStatus Status
{
    ....
    set
    {
        if (_status != value)
        {
            _status = value;
            switch(_status)
            {
                case CustomerStatus.Gold:
                    DomainEvents.Raise(CustomerIsMadeGold(this));
                    break;
                ...
            }
        }
    }

Raise メソッドは、イベントをイベント ストアに格納します。また、ローカルに登録されたイベント ハンドラーを実行することもあります。

于 2011-05-05T12:44:37.620 に答える
4

Service Layerが必要なようです。サービス レイヤーは、フロント エンドまたはコントローラーとビジネス レイヤーまたはドメイン モデルの間に位置する別の抽象化です。これは、アプリケーションへの API のようなものです。コントローラーは、サービス層にのみアクセスできます。

その後、ドメイン モデルとやり取りするのはサービス レイヤーの役割になります。

public Order GetOrderById(int id) {
  //...
  var order = orderRepository.get(id);
  //...
  return order;
}

public CreateOrder(Order order) {
  //...
  orderRepositroy.Add(order);
  if (orderRepository.Submitchanges()) {
    var email = emailManager.CreateNewOrderEmail(order);
    email.Send();
  }
  //...
}

OrderManager注文とやり取りするための「マネージャー」オブジェクトや、POCO を処理するためのサービス レイヤーで終わるのが一般的です。

UI がこれらのイベントを発生させるのは適切ですか? どのイベントが発生したかを認識し、サービス層がオブジェクトを正常に保存した後でのみ、イベントを起動できます。コントローラがドメイン イベントを発生させると、何かがおかしいように見えます。

いいえ。新しいアクションが追加され、開発者が電子メールを送信する必要があることを認識していないか忘れていると、問題が発生します。

正常に永続化された後、リポジトリはイベントを発生させる必要がありますか?

いいえ。リポジトリの責任は、データ アクセスの抽象化を提供することであり、他には何もありません。

これを行うより良い方法はありますか?オブジェクトが永続化された後にのみ、サービス層によって起動されるイベントをドメイン オブジェクトでキューに登録することはできますか?

はい、これはサービス層で処理する必要があるようです。

于 2011-05-04T16:16:04.913 に答える
1

ソリューションは、NHibernate セッション オブジェクトにこれらの拡張メソッドを実装することに基づいていることが判明しました。

質問の文言が少しわかりにくかったかもしれません。アーキテクチャの問題の全体的な理由は、手動でプロキシを解除してあらゆる種類の策略を経ない限り、NHibernate オブジェクトが常に同じ状態にあるという事実にありました。これは、どのプロパティが変更されたか、つまりどのイベントを起動するかを判断するために実行する必要はありませんでした。

最終的に失敗する可能性のある操作でイベントが発生するのを避けるために、イベントは変更が永続化された後にのみ発生する必要があるため、プロパティ セッターでこれらのイベントを発生させることはできません。

だから私がしたことは、私のリポジトリベースにいくつかのメソッドを追加することでした:

public bool IsDirtyEntity(T entity)
{
    // Use the extension method...
    return SessionFactory.GetCurrentSession().IsDirtyEntity(entity);
}

public bool IsDirtyEntityProperty(T entity, string propertyName)
{
    // Use the extension method...
    return SessionFactory.GetCurrentSession().IsDirtyProperty(entity, propertyName);
}

次に、私のサービスのSaveメソッドで、次のようなことができます (ここでは NServiceBus を使用していますが、Udi Dahan のドメイン イベントの静的クラスを使用している場合も同様に機能することを思い出してください)。

var pendingEvents = new List<IMessage>();
if (_repository.IsDirtyEntityProperty(order, "Status"))
    pendingEvents.Add(new OrderStatusChanged()); // In reality I'd set the properties of this event message object

_repository.Save(order);
_unitOfWork.Commit();

// If we get here then the save operation succeeded
foreach (var message in pendingEvents)
    Bus.Send(message);

エンティティの が保存されるまで設定されない場合があるためId(私はIdentity整数列を使用しています)、トランザクションをコミットした後、ID を取得してイベント オブジェクトにプロパティを設定する必要がある場合があります。これは既存のデータであるため、クライアントが割り当てた Id の hilo タイプに簡単に切り替えることはできません。

于 2011-05-06T13:31:49.950 に答える
1

David Glenn が言ったように、ドメイン サービスが必要だと思います。

私が遭遇した問題は、変更されたバージョンを保存する前に、サービス層がいくつかのオブジェクトのコピーを取得して、新しいバージョンと古いバージョンを比較し、どのイベントを発生させるかを決定する必要があるということでした。

ドメイン サービスには、RegisterNewOrder、CreateNoteForOrder、ChangeOrderStatus など、ドメイン エンティティで何をしたいかを明確に示すメソッドが含まれている必要があります。

public class OrderDomainService()
{
    public void ChangeOrderStatus(Order order, OrderStatus status)
    {
        try
        {
            order.ChangeStatus(status);
            using(IUnitOfWork unitOfWork = unitOfWorkFactory.Get())
            {
                IOrderRepository repository = unitOfWork.GetRepository<IOrderRepository>();
                repository.Save(order);
                unitOfWork.Commit();
            }
            DomainEvents.Publish<OrderStatusChnaged>(new OrderStatusChangedEvent(order, status));
        }

    }
}
于 2011-12-11T10:18:49.890 に答える
0

サービス層に送信されるコマンドがある場合、ドメイン イベントを持つことはうまく機能します。その後、コマンドに従ってエンティティを更新し、対応するドメイン イベントを 1 回のトランザクションで発生させることができます。

エンティティ自体をUIサービス レイヤーの間で移動する場合、どのドメイン イベントが発生したかを判断するのが非常に困難になります (場合によっては不可能になることもあります)。

于 2011-05-04T17:09:05.520 に答える