Event Sourcing and CQRS, Let's use it.
By Jérémie Chassaing on Monday, November 2, 2009, 15:45 - Domain Driven Design - Permalink
Last time, we started a very basic Event Sourcing/Domain Events/CQRS framework. Be careful, I made an edit in the nested DomainEvents+Handler<T>.Handles<E>() method, the AggregateRoot.Replay method will not work as is, but we won’t need it.
We’ll build an equally simplistic application for personal library management.
The Ubiquitous Language will be minimal.
A Book can be Registered with a Title and an ISBN.
A Book can be Lent to a Borrower at some Date for an Expected Time Span.
A Book can then be Returned. If it is Returned after Expected Time Span, the return is Late.
That’s enough for our first try.
The Command Context
The State Change Events
Here is the code for the three events that we found in the Ubiquitous language:
public class BookRegistered
{
public readonly BookId Id;
public readonly string Title;
public readonly string Isbn;
public BookRegistered(BookId id, string title, string isbn)
{
Id = id;
Title = title;
Isbn = isbn;
}
}
public class BookLent
{
public readonly BookId Id;
public readonly string Borrower;
public readonly DateTime Date;
public readonly TimeSpan ExpectedDuration;
public BookLent(BookId id, string borrower, DateTime date,
TimeSpan expectedDuration)
{
Id = id;
Borrower = borrower;
Date = date;
ExpectedDuration = expectedDuration;
}
}
public class BookReturned
{
public readonly BookId Id;
public readonly string By;
public readonly TimeSpan After;
public readonly bool Late;
public BookReturned(BookId id, string @by, TimeSpan after,
bool late)
{
Id = id;
By = by;
After = after;
Late = late;
}
}
These events will usually be serialized to the event storage and on a service bus, but here everything runs in memory.
The Book Aggregate Root
The book will need to be referenced by an identity in our system. We’ll hide a Guid behind a BookId struct :
public struct BookId : IEquatable<BookId>
{
private Guid id;
private BookId(Guid id) { this.id = id; }
public static BookId NewBookId() { return new BookId(Guid.NewGuid()); }
public bool Equals(BookId other) { return other.id.Equals(id); }
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (obj.GetType() != typeof(BookId)) return false;
return Equals((BookId)obj);
}
public override int GetHashCode() { return id.GetHashCode(); }
}
Now, the Book class itself :
public class Book : AggregateRoot<BookId>
{
private readonly BookId id;
private string title;
private string isbn;
private string borrower;
private DateTime date;
private TimeSpan expectedDuration;
public Book(BookId id, IEnumerable<object> events)
{
this.id = id;
foreach (dynamic @event in events)
Apply(@event);
}
public Book(BookId id, string title, string isbn)
{
this.id = id;
var @event = new BookRegistered(id, title, isbn);
Apply(@event);
Append(@event);
}
public override BookId Id { get { return id; } }
public void Lend(string borrower, DateTime date,
TimeSpan expectedDuration)
{
if (this.borrower != null)
throw new InvalidOperationException("The book is already lent.");
var @event =
new BookLent(id, borrower, date, expectedDuration);
Apply(@event);
Append(@event);
}
public void Return(DateTime returnDate)
{
if (borrower == null)
throw new InvalidOperationException("The book has not been lent.");
if (returnDate < date)
throw new ArgumentException(
"The book cannot be returned before being lent.");
var actualDuration = returnDate - date;
var @event = new BookReturned(
id,
borrower,
actualDuration,
actualDuration > expectedDuration);
Apply(@event);
Append(@event);
}
private void Apply(BookRegistered @event)
{
title = @event.Title;
isbn = @event.Isbn;
}
private void Apply(BookLent @event)
{
borrower = @event.Borrower;
date = @event.Date;
expectedDuration = @event.ExpectedDuration;
}
private void Apply(BookReturned @event)
{
borrower = null;
}
}
The class implements AggregateRoot<BookId> and so provides an explicitly implemented UncommittedEvents property.
The first .ctor is used to load the Aggregate Root, the second one is used to build a new Aggregate Root.
The public methods (Lend and Return) are the commands on the Aggregate Root as defined in the Ubiquitous Language.
The structure is always the same :
- Validate arguments and state
- Prepare state transition using domain logic
- Apply state transition (no domain logic should happen here)
- Append state transition to uncommitted events
The first .ctor uses dynamic to dispatch each event object on the corresponding specific Apply method. In case you implement the pattern is previous C# version, it is advised to provide a Replay method in the base class that will perform the dynamic dispatch based on reflection.
That’s all for the entity. No ORM, no mapping… easy.
The Repository
It is often clearer to provide a specific repository interface that exposes only available methods. With event sourcing, it’s not that useful… we’ll write it anyway in case you’d like to use dependency injection. The interface is part of the domain and should be in the same assembly as the entity and the events.
public interface IBookRepository
{
void Add(Book book);
Book this[BookId id] { get; }
}
The implementation will simply derive from the Repository base class, it can be in the application assembly.
internal class BookRepository :
Repository<BookId, Book>,
IBookRepository
{
protected override Book CreateInstance(BookId id,
IEnumerable<object> events)
{
return new Book(id, events);
}
}
Add and the indexer are implemented by the base class. The only thing to provide is a way to instantiate the class with expected parameters.
We could use Activator.CreateInstance or reflection to provide a generic implementation. I choose to make it simpler to read.
The Query context
The Report Database
We’ll mimic a reporting table of book lent state :
This would be the data returned from table rows :
public class BookState
{
public BookId Id { get; set; }
public string Title { get; set; }
public bool Lent { get; set; }
}
And this will hide the data table implementation :
public interface IBookStateQuery
{
IEnumerable<BookState> GetBookStates();
BookState GetBookState(BookId id);
IEnumerable<BookState> GetLentBooks();
void AddBookState(BookId id, string title);
void SetLent(BookId id, bool lent);
}
We can simply query data to report in the UI, and update data state.
Implementation will be in memory for now :
class BookStateQuery : IBookStateQuery
{
private readonly Dictionary<BookId, BookState> states =
new Dictionary<BookId, BookState>();
public IEnumerable<BookState> GetBookStates()
{
return states.Values;
}
public BookState GetBookState(BookId id)
{
return states[id];
}
public IEnumerable<BookState> GetLentBooks()
{
return states.Values.Where(b => b.Lent);
}
public void AddBookState(BookId id, string title)
{
var state = new BookState { Id = id, Title = title };
states.Add(id, state);
}
public void SetLent(BookId id, bool lent)
{
states[id].Lent = lent;
}
}
The important point here is that no domain logic occurs.
A RDBMS implementation could use an ORM or simply build DTOs from a DataReader.
The event handlers
We can now denormalize domain states to the reporting database using an event handler :
class BookStateHandler :
Handles<BookRegistered>,
Handles<BookLent>,
Handles<BookReturned>
{
private readonly IBookStateQuery stateQuery;
public BookStateHandler(IBookStateQuery stateQuery)
{
this.stateQuery = stateQuery;
}
public void Handle(BookRegistered @event)
{
stateQuery.AddBookState(@event.Id, @event.Title);
}
public void Handle(BookLent @event)
{
Console.WriteLine("Book lent to {0}", @event.Borrower);
stateQuery.SetLent(@event.Id, true);
}
public void Handle(BookReturned @event)
{
Console.WriteLine("Book returned by {0}", @event.By);
stateQuery.SetLent(@event.Id, false);
}
}
The Console.WriteLine are here to view when things happen, you would usually not use it in your production code. Logging this would not provide much benefits since all the events are already stored in the EventStorage.
Using this handler, the IBookStateQuery will be up to date with current Command Context state. In an asynchronous environment, this is where eventual consistency is introduced.
We will also add a service that will notify when a user returned a book too late :
class LateReturnNotifier :
Handles<BookReturned>
{
public void Handle(BookReturned @event)
{
if (@event.Late)
{
Console.WriteLine("{0} was late", @event.By);
}
}
}
Here again, no domain logic, we just do the infrastructure stuff, usually sending an email or a SMS.
View it in Action
class Program
{
static void Main(string[] args)
{
ISessionFactory factory = new SessionFactory(new EventStorage()); IBookStateQuery query = new BookStateQuery();
DomainEvents.RegisterHanlder(() => new BookStateHandler(query));
DomainEvents.RegisterHanlder(() => new LateReturnNotifier());
var bookId = BookId.NewBookId();
using (var session = factory.OpenSession())
{
var books = new BookRepository();
books.Add(new Book(bookId,
"The Lord of the Rings",
"0-618-15396-9"));
session.SubmitChanges();
}
ShowBooks(query);
using (var session = factory.OpenSession())
{
var books = new BookRepository();
var book = books[bookId];
book.Lend("Alice",
new DateTime(2009, 11, 2),
TimeSpan.FromDays(14));
session.SubmitChanges();
}
ShowBooks(query);
using (var session = factory.OpenSession())
{
var books = new BookRepository();
var book = books[bookId];
book.Return(new DateTime(2009, 11, 8));
session.SubmitChanges();
}
ShowBooks(query);
using (var session = factory.OpenSession())
{
var books = new BookRepository();
var book = books[bookId];
book.Lend("Bob",
new DateTime(2009, 11, 9),
TimeSpan.FromDays(14));
session.SubmitChanges();
}
ShowBooks(query);
using (var session = factory.OpenSession())
{
var books = new BookRepository();
var book = books[bookId];
book.Return(new DateTime(2010, 03, 1));
session.SubmitChanges();
}
ShowBooks(query);
}
private static void ShowBooks(IBookStateQuery query)
{
foreach (var state in query.GetBookStates())
Console.WriteLine("{0} is {1}.",
state.Title,
state.Lent ? "lent" : "home");
}
}
We start by instantiating storage for the command context (the ISessionFactory) and the query context (the IBookStateQuery). In production you’ll use persistent storages (a persistent event storage and a RDBMS). I highly recommend using a Dependency Injection Container for real size projects.
Then we wire the handlers on domain events.
The application can start.
- We register a book in the library.
- We lend it to Alice on 2009-11-02 for 14 days
- She returns it on 2009-11-08, she’s on time
- We lend it to Bob on 2009-11-09 for 14 days,
- He returns it on 2010-03-01, he’s late
The output is the following :
The Lord of the Rings is home. // written from state
Book lent to Alice // written by the book state handler
The Lord of the Rings is lent. // written from state
Book returned by Alice // written by the book state handler
The Lord of the Rings is home. // written from state
Book lent to Bob // written by the book state handler
The Lord of the Rings is lent. // written from state
Book returned by Bob // written by the book state handler
Bob was late // written by the late return notifier
The Lord of the Rings is home. // written from state
We have here a clear separation between Command that handles the domain logic and Query that handles presentation logic.
Have fun. Questions and remarks expected !
Comments
Hi Jérémie,
I've really enjoyed reading these posts - it's nice to see some of the concepts I've read about applied in code.
One of the concepts I find quite tricky is defining where aggregate boundaries lie. In this example, you've chosen Book to be your aggregate root. Is there a particular reason you chose this to be the aggregate, or am I reading too far into the example? I know it is not part of the Ubiquitous Language mentioned, but I would suspect I would've implemented "Library" as my aggregate root in this case, if for no other reason than to avoid creating the aggregate root: http://www.udidahan.com/2009/06/29/...
For us old-school C# 3.0 guys, you don't need reflection if you don't have the dynamic keyword to dispatch events. You can register each event explicitly in the aggregate root constructor and then use something like this to dispatch them:
http://gist.github.com/221878
@Jeremie,
I like how this goes full circle all the way to the reporting database.
Also, what's the intent behind the custom identifier objects, e.g. "BookId"? Do they simply hide "infrastructure concerns" such as System.Guid? Do you often need complex identifiers for your objects?
@Craig>Aggregate roots are used to enforce consitency.
Here there is no need for consistency between books in the library. That's why there's no library Aggregate Root here.
I would introduce one if I wan't to enforce rules on library's books reference uniquess (as seen in a previous post) or if complex rules involving several books state changed should occured in a single consistent operation.
A parent/child relationship does not automaticaly lead to an Aggregate Root/Child entity in domain driven design.
@Johathan> dynamic dispatch is only one C#4 trick to do this dynamic dispatch. Of course there are several other ways.
In the DDD forum, greg young said he prefered using Convention based method names and reflection. That's possibilities. Use your prefered language construct here.
The result will be the same.
About BookId, there is no need here, you can simply use Guid.
But I try to avoid native types as much as possible, it harder to mix a BookId and a BorrowerId that two Guids...
Here again, use Guids if you want to avoid extra classes...
This is indeed very interesting and useful. Helps me to better understand how one might implement CQRS. Me and my friend have are interested to see how someone would do validation using DDD and CQRS. Do you think you could write a blog post showing how you would validate that ISBN is valid as in there is actually a record somewhere in the world of that ISBN or in your reporting database that has a dump of all available ISBN records. Or yet simpler validation of unique email on customer registration, or an existing ZIP code.
Thank you,
@zilvinas> there are several level on ISBN validation.
First, format validation can ben handled by creating a ISBN value type that would wrap the string and provide parsing/format validation facilities.
Second, knowing if the ISBN really exists will require an access to an external service.
For this a service will listen to Book registration and request the external service for ISBN validity. On response, the service will tell the Book that the ISBN has been checked, or not.
Idealy, all this won't happen on Book but on a BookRegistration entity that will manage the book registration process (Title checking, ISBN checking, category selection, censorship approval...).
Once all is checked on the BookRegistration entity, the book can be registered, and the Book entity can be created.
Is there any way to download bits? Or may be you host them somewhere (like Mark do)?
@mike> Not yet. But you can already copy all this code in a console application, it's working.
I'll put a full sample when I have more time, but I'm sooooooo busy these days.
Great work, Jérémie! It was very helpful to see a simple implementation of CQS. Thank you
@chris> thanks, I'm pleased to see it helps.
I wanted it to be very simple in order not to get lost in gory implementation details of event storage, dispatch, message bus...
Hey Jeremie, how would you implement the loading of an entity from the events when those events have a large amount? I don't think it's viable to retrieve the entire history of events to reload the entity to process a single command. What technique are you using?
@leonardo> I made a new post to answer the question : http://thinkbeforecoding.com/post/2...
Great article Jeremie.
Just 1 question, you represent borrower in events as a simple full-name string. Is there any reason or just for sake of simplicity for example?
Supposed I'm using borrowerId, how would that work in other BC, say LateBookNotifier (let's assume its a separate BC). How does this BC shows the name of the borrower? Does it communicate directly with command BC using ACL? Or does it also subscribe to BorrowerRegistered event as well (hence every BC would have duplicate data of each of the borrowers, just like they do each of the books)?
In real-world app, like the one I'm dealing with, it's rarely simply command and query BCs. I have multiple BCs, in this particular case can be: LibarianCounter BC (register,lend,return book), Inventory BC (status of each book and its borrower), and Administration (get notified of late books, impose fine). So each BC will have its own storage of all books and borrowers? When more entities come into play, wouldnt this get very messy?
Thanks
This is a wonderful example, Jérémie.
I like the treatment of the BookId, hiding a Guid behind a Struct. I have a couple of questions:
Question 1: Is there a term for this technique?
Questions 2: As the BookId does not expose its data, how would this be persisted and rehydrated, say if you're using NServiceBus and SQL Server for message transportation and event store, respectively.
Thanks.
GT
@GT> C# has no syntax for alias types, so you have to do it yourself.
In F# you can just write
type EmployeId = GuidTo use if with frameworks, you usually have to hook to those frameworks type conversion if available.
Since I use my own service bus and my own persistence framework, I don't have this kind of problem, but I know that there is no simple way to do it with Linq2Sql.
For Protobuf.Net v2, I managed to use the new Api to build my own serializer/deserializer conventions that manage this.
Thanks. I guess a document store like RavenDB would enable the persistence of IDs/typed IDs in lieu of an RDBMS. Good info.