-
Notifications
You must be signed in to change notification settings - Fork 79
A Beginner's Guide To Implementing CQRS ES
This is Part 1 of a four-part series describing how to build an application in .NET using the Command-Query Responsibility Segregation and Event Sourcing patterns, as well as the CQRS.NET NuGet package.
If you haven't already, we suggest you read why would you want to implement CQRS and ES. In this series, we will build a fully-functional CQRS/ES and console application using a NuGet package called CQRS.NET; discuss the individual components of a CQRS/ES application, and discuss some benefits and drawbacks to using this particular solution.
Remember that Command-Query Responsibility Segregation states that we have separate Read and Write models as well as Event Sourcing states that we have in an append-only store that contains the changes made to our data, stored as serialised events. The full architecture looks like this:
Source: A CQRS Journey from the MSDN
In order to implement that architecture, we need to build several related pieces. Here's what we need:
- Aggregate Roots: Classes that represent an object or process work-flow in the real world, like a real shopping trolley or a bank account. Something that has state.
- Entities: Classes that represent how our data is modeled on the read side, or seen on-screen. This may be very similar at first to your aggregate roots, but will likely differ over time as your application increases in complexity.
- Commands: Classes that represent requests to action something made by the end user, that will be processed by the application.
- Command Handlers: Classes that will interpret commands and raise corresponding Events.
- Events: Classes that represent changes made to the data , and are raised as a result of actioning Commands. These classes are serialised into the Event Store. One Command may kick off several Events, but usually at least one (so you know an action has taken place).
- Event Handlers: Classes that interpret the Events raised and update any affected/impacted Entities, or trigger another command (an event stating a user was created may result in a command to send a welcome email).
- Command Bus: A class that represents the queue for the Commands. Commands placed on the bus are usually only processed by one Command Handler.
- Event Bus: A class that represents the queue for the Events. Events placed on this bus are processed by one or more appropriate Event Handlers.
- Event Store: The place where the Events are stored. For our application, this will be a SQL database.
That's a lot of classes we need to build! Thankfully, there is a NuGet package out there which makes this entire process much simpler: CQRS.NET.
CQRS.NET implements a Command Bus, Event Bus, and interfaces which can be used to define our Commands, Events, Command Handlers, Event Handlers, Entities, and Event Store. Its GitHub repository is here, with complete API Reference documentation on it.
We're going to use a simple architecture for this application, with the following aggregate roots:
- Movie: Represents a Movie, with properties for the title, running time, and release date.
- Movie Review: Represents a Review of said Movie, with properties for the review content, publication, and writer of the review. One Movie may have many Reviews, but each Review only corresponds to one Movie.
The purpose of an aggregate root is more than just holding the current values of it's properties AKA it's state. One key component of aggregates roots is to enforce business rules of the application through invariants. An invariant is a rule that must remain consistent. For example, if there was a business rule that users must validate the email address they provided is really theirs, we would be able to maintain this rule through the constraint of the Aggregate. An invariant is like validation on steroids.
Aggregates roots represent the Command side of our application, not the query side, so in some cases they don't have all their properties... they may only have the properties that the business rules use. To start off, add the nuget package CQRS.NET, at-least version 3 , and the following references from the GAC to your console project:
- System.Data.Linq
- System.Runtime.Serialization
Your first aggregate roots will look be as follows:
using System;
using Cqrs.Domain;
public class Movie : AggregateRoot<string>
{
/// <summary>
/// The identifier of this movie.
/// </summary>
public Guid Rsn
{
get { return Id; }
private set { Id = value; }
}
public Movie(Guid rsn)
{
Rsn = rsn;
}
}
public class MovieReview : AggregateRoot<string>
{
/// <summary>
/// The identifier of this review.
/// </summary>
public Guid Rsn
{
get { return Id; }
private set { Id = value; }
}
public MovieReview(Guid rsn)
{
Rsn = rsn;
}
}
Note that Movie and MovieReview inherits a class called AggregateRoot. The AggregateRoot class is part of CQRS.NET that enforces a GUID key. (The term "AggregateRoot" comes from Domain-Driven Design).
NOTE: CQRS.NET by default enforces using a GUID as their key; although support for integer based keys is supported, it's not covered in this article.
Since we currently have no business rules to enforce (invariants) or business processes to execute, the classes are pretty empty. This reduces the time reloading time and memory usage on the application.
To start off with, we have a very simple application. This results in a normalised database that looks like this:
(Remember that this database represents the Query side of our application, not the Command side.)
From this model, we can create Entities that look like this:
using System;
using System.Collections.Generic;
using System.Data.Linq.Mapping;
using System.Runtime.Serialization;
using Cqrs.Entities;
[Serializable]
[DataContract]
[Table(Name = "Movies")]
public class MovieEntity : Entity
{
[DataMember]
[Column(IsPrimaryKey = true)]
public override Guid Rsn { get; set; }
[DataMember]
[Column]
public override int SortingOrder { get; set; }
[DataMember]
[Column]
public override bool IsLogicallyDeleted { get; set; }
[DataMember]
[Column]
public string Title { get; set; }
[DataMember]
[Column]
public DateTime ReleaseDate { get; set; }
[DataMember]
[Column]
public int RunningTimeMinutes { get; set; }
[DataMember]
public List<MovieReviewEntity> Reviews { get; set; }
public MovieEntity()
{
Reviews = new List<MovieReviewEntity>();
}
}
[Serializable]
[DataContract]
[Table(Name = "MovieReviews")]
public class MovieReviewEntity : Entity
{
[DataMember]
[Column(IsPrimaryKey = true)]
public override Guid Rsn { get; set; }
[DataMember]
[Column]
public override int SortingOrder { get; set; }
[DataMember]
[Column]
public override bool IsLogicallyDeleted { get; set; }
[DataMember]
[Column]
public string Content { get; set; }
[DataMember]
[Column]
public string Reviewer { get; set; }
[DataMember]
[Column]
public string Publication { get; set; }
}
Now that we've got our Aggregate Roots (that react to Commands) and Entities (that are used by queries to display data on-screen) defined, we can start to define what kinds of Commands we can accept and how we handle them, and we will do so in Part 2 of this series, which covers creating and handling Commands and Events.