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.
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);
}
}
}
Domain Events should be raised in... the Domain. That's why they are Domain Events.
public void ExecuteCommand(MakeCustomerGoldCommand command)
{
if (ValidateCommand(command) == ValidationResult.OK)
{
Customer item = CustomerRepository.GetById(command.CustomerId);
item.Status = CustomerStatus.Gold;
CustomerRepository.Update(item);
}
}
(and then in the Customer class, the following):
public CustomerStatus Status
{
....
set
{
if (_status != value)
{
_status = value;
switch(_status)
{
case CustomerStatus.Gold:
DomainEvents.Raise(CustomerIsMadeGold(this));
break;
...
}
}
}
The Raise method will store the event in the Event Store. It may also execute locally-registered event handlers.
It sounds like you need a Service Layer. A service Layer is another abstraction that sits between your front end or controllers and your business layer or domain model. It's kind of like an API into your application. Your controllers will then only have access to your Service Layer.
It then becomes your service layer's responsibility to interact with your domain model
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();
}
//...
}
It's common to end up with 'manager' objects such as OrderManager
to interact with orders and the service layer to deal with POCOs.
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.
No. You will end up with problems if new actions are added and a developer is unaware or forgets that an email should be fired off.
Should the repository fire off events after successfully persisting?
No. the repository's responsability is to provide an abstraction over data access and nothing else
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?
Yes, it sounds like this should be handled by your service layer.
The solution turned out to be based on implementing these extension methods on the NHibernate session object.
I was probably a little unclear with the wording of the question. The whole reason for the architectural issue was the fact that NHibernate objects are always in the same state unless you manually un-proxy them and go through all sorts of machinations. That was something I didn't want to have to do to determine what properties had been changed and therefore what events to fire.
Firing these events in the property setters wouldn't work because the events should only fire after changes have been persisted, to avoid firing events on an operation that might ultimately fail.
So what I did was add a few methods to my repository base:
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);
}
Then, in my service's Save
method, I can do something like this (remember I'm using NServiceBus here, but if you were using Udi Dahan's domain events static class it would work similarly):
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);
Because in some cases the Id
of the entity might not be set until it's saved (I'm using Identity
integer columns), I might have to run through after committing the transaction to retrieve the Id to populate properties in my event objects. Since this is existing data I can't easily switch to a hilo type of client-assigned Id.
Having domain events works well if you have Commands, sent to your service layer. It then can update entities according to a command, and raise corresponding domain events in a single transaction.
If you are moving entities themselves between UI and service layer, it becomes very hard (and sometimes even impossible) to determine what domain events have occured, since they are not made explicit, but hidden under state of the entities.
I think, you shouold have a Domain Service, as David Glenn said.
The problem I ran into was that the service layer would end up having to pull a copy of some objects prior to saving the modified version to compare the new one against the old one and then decide what events should be fired.
Your Domain Service should contains methods that clearly state what you want to do with your domain Entity, like: RegisterNewOrder, CreateNoteForOrder, ChangeOrderStatus etc.
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));
}
}
}
来源:https://stackoverflow.com/questions/5886174/where-to-raise-persistence-dependent-domain-events-service-repository-or-ui