-
Notifications
You must be signed in to change notification settings - Fork 6
Domain events
Work in progress
When executing a domain behaviour on an aggregate root domain entity inside a database transaction, it is always a good idea to keep the transaction small and short. Some processing does not have to be necessarily part of the database transaction, and can be done later (e.g. audit, sending email, subsequent domain processing, etc). CoreDdd supports domain events to defer some processing to a later time. The domain event is raised from a domain method on a domain entity, and announces that some domain activity has happened.
CoreDdd domain event needs to implement IDomainEvent interface. Example of a domain event:
public class ShipCargoPolicyItemAddedDomainEvent : IDomainEvent
{
public int PolicyId { get; set; }
public int ShipId { get; set; }
}Example of raising a domain event from a domain code:
public class Policy : Entity, IAggregateRoot
{
...
public virtual void AddShipCargoPolicyItem(ShipCargoPolicyItemArgs args)
{
var shipCargoPolicyItem = new ShipCargoPolicyItem(args);
_items.Add(shipCargoPolicyItem);
DomainEvents.RaiseEvent(new ShipCargoPolicyItemAddedDomainEvent
{
PolicyId = Id,
ShipId = args.Ship.Id
});
}
...
}The last bit is to implement a domain event handler(s) (there can be multiple domain event handlers for one domain event):
public class ShipCargoPolicyItemAddedDomainEventHandler : IDomainEventHandler<ShipCargoPolicyItemAddedDomainEvent>
{
public void Handle(ShipCargoPolicyItemAddedDomainEvent domainEvent)
{
// handle the domain event here
}
}.
CoreDdd supports two ways of domain event handling:
- immediate handling of the domain events when raised
- delayed handling of the domain events
The domain events handlers are executed immediately when the domain event is raised from the domain code running in a database transaction. A domain event handler should not do much - for instance it should not do any database activity or any long processing, as it is executed as a part of the database transaction. Ideally the domain event handler should just publish a message over a message bus notifying that something has happened, and subscribers of the message will handle the message, possibly in a different process or machine. Some message bus libraries like Rebus or NServiceBus allows to enlist the message publishing into an ambient transaction (TransactionScope) together with the database transaction, making sure messages will not be published in a case of the database transaction failure.
The following code initializes the domain events for an immediate handling when raised:
var domainEventHandlerFactory = ioCContainer.Resolve<IDomainEventHandlerFactory>();
DomainEvents.Initialize(domainEventHandlerFactory);Domain event handler factory is needed to initialize domain events. Either implement the factory manually, or resolve it from your favourite IoC container. When resolving it from an IoC container, you need to register the domain event handlers first. Here is Castle.Windsor example:
private void _RegisterDomainEventHandlers(WindsorContainer ioCContainer)
{
ioCContainer.Register(
Classes
.FromAssemblyContaining<ShipCargoPolicyItemAddedDomainEvent>()
.BasedOn(typeof(IDomainEventHandler<>))
.WithService.FirstInterface()
.Configure(x => x.LifestyleTransient()));
}The factory itself needs to be registered as well, Castle.Windsor example:
ioCContainer.AddFacility<TypedFactoryFacility>();
ioCContainer.Register(
Component.For<IDomainEventHandlerFactory>().AsFactory()
);The domain event handlers are not executed immediately when the domain event is raised from the domain code running in a database transaction, but are raised after the database transaction commits, by explicitly calling DomainEvents.RaiseDelayedEvents();. Again, a domain event handler should not do much - for instance it should not do any database activity or any long processing. Even though it is executed after the database transaction, a long running event handler would cause an overall slow web request handling for instance.
The following code initializes the domain events for delayed handling:
var domainEventHandlerFactory = ioCContainer.Resolve<IDomainEventHandlerFactory>();
DomainEvents.Initialize(domainEventHandlerFactory, isDelayedDomainEventHandlingEnabled: true);Here is a pros and cons of immediate and delayed domain event handling.
Immediate domain event handling
Pros:
- when possible to use an ambient transaction (
TransactionScope), and a domain event handler just publishes a message over a message bus where both the database transaction and the message publishing are enlisted into the ambient transaction, they both succeed or both fail.
Cons:
- domain event handler is executed as a part of the database transaction
Delayed domain event handling
Pros:
- domain event handlers are executed after the database transaction is committed, making the database transaction shorter
Cons:
- if the server crashes right after the database transaction commit, the domain event handlers will not be executed