December 2016

Volume 31 Number 13

[Cutting Edge]

Rewrite a CRUD System with Events and CQRS

By Dino Esposito

Dino EspositoThe world’s full of classic Create, Read, Update, Delete (CRUD) systems built around a relational database and padded with chunks of business logic, sometimes buried in stored procedures and sometimes caged in black box components. At the core of such black boxes are the four operations of CRUD: creating new entities, reading, updating and deleting. At a sufficiently high level of abstraction, that’s all of it—any system is, to some extent, a CRUD system. The entities might be quite complex at times and take more the form of an aggregate.

In Domain-Driven Design (DDD), an aggregate is a business-­related cluster of entities with a root object. Hence, creating, updating or even deleting an entity might be subject to several intricate business rules. Even reading the state of an aggregate is usually problematic, mostly for UX reasons. The model that works to alter the state of the system isn’t necessarily the same model that works for presenting data to users in all use cases.

Taking the abstraction level of CRUD to its highest leads straight to separating what alters the state of a system from what simply returns one or more views of it. This is the raw essence of Command and Query Responsibility Segregation (CQRS), namely the neat segregation of command and query responsibilities.

However, there’s more to it that software architects and developers must take into account. The state of the system is altered at the command stack and, in concrete terms, that’s where aggregates are first created; that’s also where the same aggregates are later updated and deleted. And that’s precisely the point to rethink.

Preserving history is critical for almost any software system. As software is written to support ongoing business, learning from the past is crucial for two reasons: to avoid missing a single thing that happened and to improve services to customers and employees.

In the May 2016 (msdn.com/magazine/mt703431) and June 2016 (msdn.com/magazine/mt707524) installments of this column, I presented ways to extend the classic CRUD to a historical CRUD. In my August 2016 (msdn.com/magazine/mt767692) and October 2016 (msdn.com/magazine/mt742866) columns, instead, I presented an Event-Command-­Saga (ECS) pattern and a Memento FX framework (bit.ly/2dt6PVD) as the building blocks of a new way to express the business logic that meets everyday needs.

In this column and the next, I’ll address the two aforementioned benefits of preserving history in a system by rewriting a booking demo application (the same I used in the May and June columns) with CQRS and event sourcing.

The Big Picture

My sample application is an internal booking system for meeting rooms. The main use case is a logged user that scrolls a calendar and books one or more free slots on a given room. The system manages entities such as Room, RoomConfiguration and Booking, and, as you can imagine, conceptually the whole application is about adding and editing rooms and configurations (that is, when the room is open for booking and length of single slots), and adding, updating, and canceling reservations. Figure 1 offers a glimpse of the actions that users of the system are able to perform and how they’ll be architected in a CQRS system according to the ECS pattern.

User Actions and High-Level Design of the System
Figure 1 User Actions and High-Level Design of the System

A user can enter a new reservation, move and cancel it, and even check into the room so that the system knows the reserved room is actually being used. The workflow behind each action is handled in a saga and the saga is a class defined in the command stack. A saga class is made up of handler methods, each processing a command or an event. Placing a reservation (or moving an existing reservation) is a matter of pushing a command to the command stack. Generally speaking, pushing a command can be as simple as directly invoking the corresponding saga method or it can go through the services of a bus.

To preserve history, you need to track at least all the business effects of any processed commands. In some cases, you also might want to track the original commands. A command is a data-transfer object carrying some input data. A business effect of executing a command through a saga is an event. An event is a data-transfer object carrying the data that fully describes the event. Events are saved to a specific data store. There are no strict constraints on the storage technology to use for events. It can be a plain relational database management system (RDBMS) or it can be a NoSQL data store. (Refer to the October column for the setup of MementoFX and RavenDB and bus.)

Coordinating Commands and Queries

Let’s say that a user places a command to book a slot on a given room. In an ASP.NET MVC scenario, the controller gets the posted data and places a command to the bus. The bus is configured to recognize a few sagas and each saga declares the commands (and/or events) it’s interested in to handle. Hence, the bus dispatches the message to the saga. The input of the saga is the raw data that users typed in the UI forms. The saga handler is responsible for turning received data in an instance of an aggregate that’s consistent with the business logic.

Let’s say that the user clicks to book, as shown in Figure 2. The controller method triggered by the button receives the ID of the room, the day and time, and the user name. The saga handler must turn this into a Booking aggregate tailor made to deal with the expected business logic. Business logic will reasonably address concerns in the area of permissions, priorities, costs and even plain concurrency. However, at the very minimum the saga method has to create a Booking aggregate and save it.

Booking a Meeting Room in the Sample System
Figure 2 Booking a Meeting Room in the Sample System

At first sight, the code snippet in Figure 3 is no different from a plain CRUD except for its use of a factory and the outstanding Repository property. The combined effect of factory and repository writes in the configured event stores all the events triggered within the implementation of the Booking class.

Figure 3 Structure of a Saga Class

public class ReservationSaga : Saga,
  IAmStartedBy<MakeReservationCommand>,
  IHandleMessages<ChangeReservationCommand>,
  IHandleMessages<CancelReservationCommand>
{
   ...
  public void Handle(MakeReservationCommand msg)
  {
    var slots = CalculateActualNumberOfSlots(msg);
    var booking = Booking.Factory.New(
      msg.FullName, msg.When, msg.Hour, msg.Mins, slots);
    Repository.Save(booking);
  }
}

In the end, the repository doesn’t save a record with the current state of a Booking class where properties are in some way mapped to columns. It just saves business events to the store and in the end at this stage you know exactly what’s happened to your booking (when it was created and how), but you don’t have all the classic information ready to display to the user. You know what’s happened, but you don’t have anything ready to show. The source code of the factory is shown in Figure 4.

Figure 4 Source Code of the Factory

public static class Factory
{
  public static Booking New(string name, DateTime when,
    int hour, int mins, int length)
  {
    var created = new NewBookingCreatedEvent(
      Guid.NewGuid(), name.Capitalize(), when,
      hour, mins, length);
    // Tell the aggregate to log the "received" event
    var booking = new Booking();
    booking.RaiseEvent(created);
    return booking;
  }
}

No properties of the newly created instance of the Booking class are touched in the factory, but an event class is created and populated with the actual data to store in the instance, including the capitalized name of the customer and the unique ID that will perma­nently track the reservation throughout the system. The event is passed to the RaiseEvent method, part of the MementoFX framework, because it’s the base class of all aggregates. RaiseEvent adds the event to an internal list that the repository will go through when “saving” the instance of the aggregate. I used the term “saving” because that’s just what happens, but I put quotes around it to emphasize that it’s a different type of action than in a classic CRUD. The repository saves the event that a reservation was created with the specified data. More precisely, the repository saves all the events logged to an instance of the aggregate during the execution of a business workflow, namely a saga handler method, as shown in Figure 5.

Saving Events Versus Saving State
Figure 5 Saving Events Versus Saving State

But tracking the business event resulting from a command is not enough.

Denormalizing Events to the Query Stack

If you look at CRUD through the lens of preserving data history, you see that creating and reading entities don’t affect history, but the same can’t be said for updating and deleting. An event store is append-only and updates and deletions are just new events related to the same aggregates. Having a list of events for a given aggregate, though, tells you everything about the history except the current state. And the current state is just what you need to present to users.

Here’s where denormalizers fit in. A denormalizer is a class built as a collection of event handlers, just like those being saved to the event store. You register a denormalizer with the bus and the bus dispatches events to it every time it gets one. The net effect is that a denormalizer written to listen to the created event of a booking is given a chance to react whenever one is triggered.

A denormalizer gets the data in the event and does whatever you need it to do, for example, keeping an easy-to-query relational database in sync with recorded events. The relational database (or a NoSQL store or a cache, if that’s easier or more beneficial to use) belongs to the query stack and its API isn’t given access to the stored list of events. What’s more, you can have multiple denormalizers creating ad hoc views of the same raw events. (I’ll delve deeper into this aspect in my next column.) In Figure 1, the calendar from which a user picks a slot is populated from a plain relational database that’s maintained in sync with events by the action of a denormalizer. See Figure 6 for the denormalizer class code.

Figure 6 Structure of a Denormalizer Class

public class BookingDenormalizer :
  IHandleMessages<NewBookingCreatedEvent>,
  IHandleMessages<BookingMovedEvent>,
  IHandleMessages<BookingCanceledEvent>
{
  public void Handle(NewBookingCreatedEvent message)
  {
    var item = new BookingSummary()
    {
      DisplayName = message.FullName,
      BookingId = message.BookingId,
      Day = message.When,
      StartHour = message.Hour,
      StartMins = message.Mins,
      NumberOfSlots = message.Length
    };
    using (var context = new MfxbiDatabase())
    {
      context.BookingSummaries.Add(item);
      context.SaveChanges();
    }  }
  ...
}

With reference to Figure 5, denormalizers provide a relational CRUD for read purposes only. The output of denormalizers is often called the “read model.” Entities in the read model don’t typically match the aggregates used to generate events as they are mostly driven by the needs of the UI.

Updates and Deletions

Suppose now the user wants to move a previously booked slot. A command is placed with all the details of the new slot and a saga method takes care of writing a Moved event for the given booking. The saga needs to retrieve the aggregate and needs it in the updated state. If denormalizers just created a relational copy of the aggregate’s state (therefore the read model nearly coincides with the domain model), you can get the updated state from there. Otherwise, you create a fresh copy of the aggregate and run all logged events on it. At the end of the replay, the aggregate is in the most updated state. Replaying events isn’t a task you have to perform directly. In MementoFX you get an updated aggregate with a line of code within a saga handler:

var booking = Repository.GetById<Booking>(message.BookingId);

Next, you apply to the instance any business logic you need. The business logic generates events and events are persisted through the repository:

booking.Move(id, day, hour, mins);
Repository.Save(booking);

If you use the Domain Model pattern and follow DDD principles, the Move method contains all the domain logic and events. Otherwise, you run a function with any business logic and raise events to the bus directly. By binding another event handler to the denormalizer, you have a chance to update the read model.

The approach isn’t different for canceling a booking. The event of canceling a booking is a business event and must be tracked. This means you might want to have a Boolean property in the aggregate to perform logical deletion. In the read model, though, deletion could be blissfully physical depending on whether your application is going to query the read model for canceled bookings. An interesting side effect is that you can always rebuild the read model by replaying events from the beginning or from a recovery point. All it takes is to create an ad hoc tool that uses the event store API to read events and call denormalizers directly.

Using the Event Store API

Look at the selection of the dropdown list in Figure 2. The user wants to stretch the booking as long as possible from the starting time. The business logic in the aggregate must be able to figure that out and to do so it must access the list of bookings in the same day later than the starting time. That’s no big deal in a classic CRUD, but MementoFX lets you query for events, as well:

var createdEvents = EventStore.Find<NewBookingCreatedEvent>(e =>
  e.ToDateTime() >= date).ToList();

The code snippet returns a list of NewBookingCreated events following the given time. However, there’s no guarantee that the booking created is still active and hasn’t been moved to another slot. You really need to get the updated state of those aggregates. The algorithm is up to you. For example, you can filter out from the list of Created events the bookings no longer active and then get the ID of the remaining bookings. Finally, you check the actual slot against the one you want to stretch while avoiding overlapping. In the source code of this article, I coded all this logic in a separate (domain) service in the command stack.

Wrapping Up

Using CQRS and event sourcing isn’t limited to particular systems with high-end requirements for concurrency, scalability and performance. With infrastructure available that lets you work with aggregates and workflows, any of today’s CRUD systems can be rewritten in a way that brings many benefits. Those benefits include:

  • Preserving history of data
  • A more effective and resilient way of implementing business tasks and changing tasks to reflect business changes with limited effort and risk of regression
  • Because events are immutable facts, they are trivial to copy and duplicate and even read models can be regenerated programmatically at will

This means that the ECS pattern (or CQRS/ES as it’s sometimes referred) has a tremendous potential for scalability. Even more, the MementoFX framework here is helpful because it simplifies common tasks and offers the aggregate abstraction for easier programming.

MementoFX pushes a DDD-oriented approach, but you can use the ECS pattern with other frameworks and other paradigms such as the functional paradigm. There’s one more benefit, and probably the most relevant. I’ll tell you about it in my next column.


Dino Esposito is the author of “Microsoft .NET: Architecting Applications for the Enterprise” (Microsoft Press, 2014) and “Modern Web Applications with ASP.NET” (Microsoft Press, 2016). A technical evangelist for the .NET and Android platforms at JetBrains, and frequent speaker at industry events worldwide, Esposito shares his vision of software at software2cents.wordpress.com and on Twitter: @despos.

Thanks to the following Microsoft technical expert for reviewing this article: Andrea Saltarello


Discuss this article in the MSDN Magazine forum