Handling Events on the Read Side

I said that our Read Model Projections are special kinds of Event Handlers that are registered to the Bus. What makes them unique? Their role is to simply update the Read Model persistence mechanism. This persistence mechanism is optimized for Reads. It might be a denormalized SQL Server table, Redis, OLAP Cube, Neo4J, etc.

While this example is simple, being that Nick just needs to know what Roast Schedules he has created, you can probably imagine the unique requirements of First Pop Coffee’s User Interface. Nick needs to be able to make the right decisions about the management of his coffee roasting business. This is the deciding factor in how the Team will shape and decide what type of persistence infrastructure is best for each Read Model - what does Nick need to know to make a decision?

For illustration purposes, we’re going to build a quick in-memory implementation of the Roast Planning Read Model. In future posts, we’ll come back and look into details around production-level solutions.

For the Read Model’s persistence store, we’ll start with a vanilla Entity Framework DbContext. Another, more performant, solution would be Dapper by Stack Exchange but for now we’re going to use Entity Framework with fake, in-memory DbSet instances. The implementation of FakeDbSet I’m using comes from the article Entity Framework Testing with your Own Test Doubles (EF6 onwards).

The beauty of building this application with CQRS in mind from the beginning is that we can forget about the excessive DTO mapping that we’ve been used to when our Write Model is our Read Model as well. In the traditional CRUD architecture we know so well, DbContext is queried from and the resulting Entities are mapped to View Model DTOs for the Presentation Layer. There’s nothing wrong with this architecture, however through CQRS we’ve enabled ourselves to reduce the impedence mismatch between our DTOs and our Domain Model persistence store. Our database can now store flat, denormalized-for-fast-reads objects.

We can simply store RoastScheduleViewModel in our DbContext like so:

public interface IRoastPlanningContext
{
    DbSet<RoastScheduleViewModel> RoastSchedules { get; }
    int SaveChanges();
}

public class RoastPlanningContext : IRoastPlanningContext
{
    public DbSet<RoastScheduleViewModel> RoastSchedules { get; set; }

    public RoastPlanningContext(FakeDbSet<RoastScheduleViewModel> roastSchedules)
    {
        this.RoastSchedules = roastSchedules;
    }

    public int SaveChangesCount { get; private set; }
    public int SaveChanges()
    {
        this.SaveChangesCount++;
        return 1;
    }
}

This DbContext is only going to be used by our Read Model Projections to insert data into our Read Model SQL Database upon handling new Events emitted by the Event Store in our Write Model.

public class RoastScheduleView : IEventHandler
{
    private readonly IRoastPlanningContext _roastPlanningContext;

    public RoastScheduleView(IRoastPlanningContext roastPlanningContext)
    {
        _roastPlanningContext = roastPlanningContext;
    }
    public void Handle(RoastScheduleCreatedEvent message)
    {
        var newRoastSchedule = new RoastScheduleViewModel(message.RoastScheduleId, message.RoastWeekStartsOn);
        _roastPlanningContext.RoastSchedules.Add(newRoastSchedule);
        _roastPlanningContext.SaveChanges();
    }
}

Although this is how we’re making our Read Model consistent with our Write Model, we’re not going to use IRoastPlanningContext for our Presentation Layer. We’re going to use what’s called a Read Model Facade. We’ll cover that in the next post. Stay tuned!


continue reading…

previous…


I will be releasing most of this content only to subscribers. Make sure you sign up by clicking the big red button!


Related Posts:

Tweet
comments powered by Disqus