Handling domain events

Datetime:2016-08-22 22:52:30          Topic: Assembler           Share

Last time I wrote a post about thecommands. Events are quite similar to the commands – the main difference between these 2 patterns is that the events are related to the things that have actually happened to our entity e.g. by invoking the command. They’re also a core part of the DDD (Domain Driven Design) and can be easily implemented within our software solution.

Let’s define our event interface at first:

//Marker
public interface IEvent
{
}

Now, we can move further and take a look into the base Entity class:

public interface IEntity
{
    IEnumerable<IEvent> Events { get; }
}
 
public abstract class Entity : IEntity
{
    private readonly IDictionary<Type, IEvent> _events = new Dictionary<Type, IEvent>();
 
    public IEnumerable<IEvent> Events => _events.Values;
 
    protected void AddEvent(IEvent @event)
    {
        _events[@event.GetType()] = @event;
    }
 
    protected void ClearEvents()
    {
        _events.Clear();
    }
}

In case you were wondering why I’m using a dictionary here – thanks to this type of a collection I can avoid adding multiple events of the same type e.g. UserEmailChanged and then invoking them multiple times. Yet, again – it’s totally up to you, so feel free to choose the preferable solution.

So far so good, eventually we can define our event once the user’s email was changed.

public class UserEmailChanged : IEvent
{
    public Guid Id { get; }
 
    public UserEmailChanged(Guidid)
    {
        Id = id;
    }
}

Remember that you’d better keep your events very thin . Sure thing, you could pass the whole user object, but think outside of the box – what if you want to scale your application on multiple servers? By passing the minimum amount of the information required to identify the entity (e.g. it’s id and maybe an old value along with the new one) you will be able to push such event by message bus or any other communication protocol and handle easily by some external application, without worrying about (de)serialization issues and huge traffic due to the large objects.

And here’s our user:

public class User : Entity
{
    public string Email { get; protected set; }
    public string Password { get; protected set; }
    public DateTime UpdatedAt { get; protected set; }
 
    protected User()
 {
 }
 
 public User(string email)
 {
    SetEmail(email);
 }
 
 public void SetEmail(string email)
 {
    if (string.IsNullOrWhiteSpace(email))
        throw new ArgumentException("Email can not be empty.", nameof(email));
 
    if (Email.Equals(email))
        return;
 
    Email = email;
    UpdatedAt = DateTime.UtcNow;
    AddEvent(new UserEmailChanged(Id));
 }
}

As you can see, whenever the user changes the email address, we will store such event.

And here comes to the question, how to handle this event? Well, quite easily, so let’s start with the event handler definition:

public interface IEventHandler<in T> where T : IEvent
{
    TaskHandleAsync(T @event);
}
 
public class UserEmailChangedHandler : IEventHandler<UserEmailChanged>
{
    public async TaskHandleAsync(UserEmailChanged @event)
    {
        //Fetch the user from database by id, log this event, store some data etc.
    }
}

And the event dispatcher based on the Autofac IoC container:

public interface IEventDispatcher
{
    TaskDispatchAsync<T>(params T[] events) where T : IEvent;
}
 
public class EventDispatcher : IEventDispatcher
{
    private readonly IComponentContext_context;
 
    public EventDispatcher(IComponentContextcontext)
    {
        _context = context;
    }
 
    //I need to find out how to get generic handler in Autofac
    public async TaskDispatchAsync<T>(params T[] events) where T : IEvent
    {
        foreach (var @event in events)
        {
            if (@event == null)
                throw new ServiceException("Event can not be null.");
 
            var eventType = @event.GetType();
            var handlerType = typeof(IEventHandler<>).MakeGenericType(eventType);
            object handler;
            _context.TryResolve(handlerType, out handler);
 
            if (handler == null)
                return;
 
            //GetRuntimeMethods() works with .NET Core, otherwise simply use GetMethod()
            var method = handler.GetType()
                .GetRuntimeMethods()
                .First(x => x.Name.Equals("HandleAsync"));
 
            await (Task)method.Invoke(handler, new object[] { @event });
        }
    }
}

Speaking of the Autofac , we have to register our handlers and dispatcher, so that IoC magic will be able to happen:

public class EventModule : Module
{
    protected override void Load(ContainerBuilderbuilder)
    {
        builder.RegisterType<EventDispatcher>()
            .As<IEventDispatcher>()
            .InstancePerLifetimeScope();
 
        var assembly = Assembly.Load(new AssemblyName("MyAssemblyNamespace"));
        builder.RegisterAssemblyTypes(assembly).AsClosedTypesOf(typeof(IEventHandler<>));
    }
}
 
//Remember to register the module within the IContainer
public static class Container
{
    public static IContainerResolve()
    {
        var builder = new ContainerBuilder();
        builder.RegisterModule<EventModule>();
 
        return builder.Build();
    }
}

And that’s pretty much it. What you can do now, is, for example, the following:

public interface IUserService
{
    TaskCreateAsync(string email);
}
 
public class UserService : IUserService
{
    private readonly IEventDispatcher_eventDispatcher;
 
    public UserService(IEventDispatchereventDispatcher)
    {
        _eventDispatcher = eventDispatcher;
    }
 
    public async TaskCreateAsync(string email)
    {
        var user = new User(email);
        //Store the user somehwere safe using the repository etc.
        ...
        //Dispatch all of the user events
        await _eventDispatcher.DispatchAsync(user.Events.ToArray());
    }
}

My approach to the events is mostly based on the Jimmy Bogard’s post which is an opposite to the static pattern described by the Udi Dahan here .





About List