Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Where to raise persistence-dependent domain events - service, repository, or UI?

My ASP.NET MVC3 / NHibernate application has a requirement to fire off and handle a variety of events related to my domain objects. For example, an Order object might have events like OrderStatusChanged or NoteCreatedForOrder. In most cases these events result in an email being sent, so I can't just leave them in the MVC app.

I've read through Udi Dahan's Domain Events and dozens of other thoughts on how to do this sort of thing, and I decided on using an NServiceBus-based host that handles event messages. I've done a few proof-of-concept tests and this seems to work well.

My question is what application layer should actually raise the events. I don't want to fire the events until the object in question has been successfully persisted (can't send an email that a note was created if the persistence failed).

Another concern is that in some cases an event is tied to an object which is beneath an aggregate root. In the example above, a Note is saved by adding it to the Order.Notes collection and saving the order. This poses a problem in that it makes it tough to evaluate what events should get fired when an Order is saved. I'd like to avoid having to pull a current copy of the object and look for differences prior to saving the updated copy.

  • Is it appropriate for the UI to raise these events? It knows what events have occurred and can fire them only after successfully having the service layer save the object. Something just seems wrong about having a controller firing off domain events.

  • Should the repository fire off events after successfully persisting?

  • Should I separate the events altogether, have the repository store an Event object which is then picked up by a poller service and then turned into an event for NServiceBus (or handled directly from the poller service)?

  • Is there a better way to do this? Maybe having my domain objects queue up events which are fired by the service layer only after the object is persisted?

  • Update: I have a service layer, but it seems cumbersome and excessive to have it go through a comparison process to determine what events should be fired when a given aggregate root is saved. Because some of these events are granular (e.g. "order status changed"), I think I'd have to retrieve a DB copy of the object, compare the properties to create events, save the new object, then send the events to NServiceBus when the save operation completed successfully.

Update

What I ended up doing, subsequent to the answer I posted below (way below), was to build into my domain entities an EventQueue property that was a List<IDomainEvent>. I then added events as changes to the domain merited it, which allowed me to keep the logic within the domain, which I believe is appropriate since I'm firing events based on what's going on within an entity.

Then, when I persist the object in my service layer, I process that queue and actually send the events to the service bus. Initially, I was planning on using a legacy database that used identity PKs, so I had to post-process these events to populate the ID of the entity, but I ultimately decided to switch to a Guid.Comb PK which allows me to skip that step.

like image 491
Josh Anderson Avatar asked May 04 '11 15:05

Josh Anderson


People also ask

How do you raise a domain event?

The deferred approach to raise and dispatch events Instead of dispatching to a domain event handler immediately, a better approach is to add the domain events to a collection and then to dispatch those domain events right before or right after committing the transaction (as with SaveChanges in EF).

What is integration event?

Integration events are generally used for integrating with other service boundaries. This then means we're moving work out of process by leveraging a message broker. Consumers will each individually process the event in isolation without having any effect on the publisher or any other consumers.


1 Answers

My solution is that you raise events in both Domain layer and service layer.

Your domain:

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

Your service:

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);         }     } } 
like image 173
Yanfeng Tian Avatar answered Sep 22 '22 05:09

Yanfeng Tian