Index

Part 0 – Introduction
Part 1 – Persistence
Part 2 - Domain Events

There are many advantages to building a full domain model. However it is not immediately obvious how business logic inside classes mapped using an ORM can invoke behaviours outside the domain. Invoking services directly involves tight coupling and is difficult to test. Doing dependency injection into domain classes is problematic as they are controlled by another container (the ORM).

As with so many things Udi Dahan has an excellent summary of the issue and an effective solution for it. See How to create fully encapsulated Domain Models, Domain Events – Take 2, Domain Events – Salvation. I consider his final solution excellent and have simply integrated it into Avernus.

The domain event support revolves around three primary types:

  • IDomainEvent is a marker interface that is applied to data classes to indicate they are a raiseable event.
  • IHandleDomainEvents<T> is the interface that defines the contract to be implemented by anything wishing to handle domain events. Multiple types may implement this interface, the order in which they are invoked is not defined.
  • DomainEvents is a static class that handles raising events.

We start by configuring DomainEvents to use the appropriate StructureMap container:

DomainEvents.Container = ObjectFactory.Container;

This specifies the default container, but a custom container may also be used.

A domain event is simply a data transfer object that implements IDomainEvent. A typical example is contained in the Avernus Store sample code:

public class OrderAddedEvent : IDomainEvent
{
    public Order Order { get; set; }
}

Note that this is named in the past tense. Domain events signal things that have happened in the domain rather than indicating that things are happening or will happen. This rule should only be violated under exceptional circumstances. As domain events are transient it is acceptable to have publically settable properties. Event handlers should not modify these properties. If they have a need to modify the domain then they can make (constrained) calls using the instances in the event object.

A typical domain event handler looks like:

public class OrderAddedHandler : IHandleDomainEvents<OrderAddedEvent>
{
    public void Handle(OrderAddedEvent instance)
    {
        Console.WriteLine(instance.Order.ReferenceNumber);
    }
}

Handlers are retrieved from the StructureMap container and must therefore be registered with the container. This can be accomplished by adding a call to ConnectImplementationsToTypesClosing in the StructureMap configuration:

initialise.Scan(scan =>
    {
        scan.TheCallingAssembly();
        scan.ConnectImplementationsToTypesClosing(typeof(IFetchingStrategy<>));
        scan.ConnectImplementationsToTypesClosing(typeof(IHandleDomainEvents<>));
    });

Once the handler has been registered we can raise a domain event. Consider the AddOrder business method:

public virtual void AddOrder(string referenceNumber)
{
    var order = new Order(this, referenceNumber);

    _orders.Add(order);

    DomainEvents.Raise(new OrderAddedEvent { Order = order });
}

Once the order has been created correctly we create a new instance of OrderAddedEvent and set its Order property to be the new instance. We then pass this to the static Raise method. This method uses the StructureMap container to find handlers that can handle the event and invokes each of them passing them the event instance.

If you invoke the Avernus Store sample you will get output something like:

image

What we can see here is that the output of the event handler (which prints the Order reference ABC012345) runs before NHibernate persists the new changes. This is important as it shows that the event handlers are running within the bounds of the transaction being used for the business method. As such other actions (such as sending transactional messages) may participate in that transaction. Additionally any failure in the event handler will cause the transaction to roll back. Event handlers should be written with this consideration in mind.

Although handlers are requested from the container it is also possible to register them manually. This feature permits scenarios where configuring a container is undesirable, that is to say testing. The DomainEvents type has a static method Register that can be used to add event handlers directly. The ClearCallbacks method is used to clear these added handlers to maintain test isolation.

The primary implementation difference between the version in Avernus and Udi Dahan’s original is that the Avernus version is (as per the rest of Avernus) tied to StructureMap. Additionally I have made the internals somewhat LINQier but this has no effect on the external interface.