.NET Backend

CQRS/ES #2 Domain objects

In the first part of our journey, we became familiar with CQRS and Event Sourcing. In this episode, we’re going to implement a few classes in our brand new system. Before we start let’s discuss a little bit about a business problem that we’ll try to model. At first, I thought about something very easy like a bookstore which would allow users only to buy some books. But that would be boring, wouldn’t be? By chance, a few weeks ago I received the kind of interesting recruitment objective from some company. Implementation of… Google Calendar. Sounds good? Well, I hope so!

 

Project assumptions

First, we need to specify some requirements that our application should meet. However, we will focus only on the main functionalities such as:

  • Creating a single event
  • Creating event cycles (daily/weekly/monthly/yearly)
  • Editing single event
  • Editing event in cycles (all events/single event/future events)

 

In our case, word „event” is quite unfortunate since we will introduce some system events and ES in the future. Therefore we will use another name for that object – Calendar Item.

 

Read Database

Let’s start with modeling entities, which will be included in our database for reading. Thanks to that we will be able more easily to implement domain objects. For meeting first two requirements such a model should be enough for us:

 

model

 

To understand this model better, consider a short example. We would like to add a calendar item called „gym” which will regularly be repeated every Wednesday and Friday at 7 PM. Let’s say we create that on July 24 (Sunday). Our data would look like this:

 

data

 

I think that the data in the CalendarItems table does not require any explanation. But what have we just inserted into CalendarItemCycles? There are two rows. Each of them represents one day of the week. This means that each CalendarItem can assign up to seven cycles. The first row is a Wednesday cycle, which begins July 27, and the second is Friday cycle (starting July 29). Both cycles don’t have a defined EndDate (NULL value) suggesting that CalendarItem should always appear on our calendar. Type informs about the frequency of the cycle (2 means weekly), and Interval column determines the length of one cycle. In our case, a value of 1 tells us that the cycle is renewed every week. In both tables, the IsDeleted flag is used for soft deleting.

 

Domain objects

Knowing the read database model, we can finally implement our Domain Objects which look like this:

 


public class CalendarItem : AggregateRoot
{
    public string UserId { get; set; }

    public string Name { get; set; }

    public string Description { get; set; }

    public DateTime StartDate { get; set; }

    public DateTime EndDate { get; set; }
   
    List<CalendarItemCycle> Cycles { get; } = new List<CalendarItemCycle>();


    public CalendarItem(Guid id, string userId, string name, string description, 
        DateTime startDate, DateTime endDate, IEnumerable<Contracts.Commands.CalendarItemCycle> cycles)
    {
       // implementation in next parts
    }

    public CalendarItem()
    {
        
    }
}


class CalendarItemCycle
{
    public DateTime StartDate { get; set; }

    public DateTime? EndDate { get; set; }

    public int Interval { get; set; }

    public CalendarItemCycleType Type { get; set; }

    public CalendarItemCycle(DateTime startDate, DateTime? endDate, int interval, CalendarItemCycleType type)
    {
        StartDate = startDate;
        EndDate = endDate;
        Interval = interval;
        Type = type;
    }
}


 

Of course, our domain objects don’t have any business logic yet. Notice that CalendarItem inherits from a class called AggreagteRoot. I described this pattern in the previous part, so if you’re not familiar with that just stop here for a moment and come back to read when you’ll be ready. AggreagteRoot will have a few liabilities in our code (but we won’t break SRP):

  • defines the unique domain object in our application (that’s why CalendarItemCycle is not also AggregateRoot)
  • stores events generated within the Aggregate
  • restricts access to domain objects other than root

 

Implementation of AggragateRoot looks as below:

 


public abstract class AggregateRoot : IAggregateRoot
{
    public Guid Id { get; set; }

    public int Version { get; set; }

    protected List<IEvent> Events { get; set; } = new List<IEvent>();

    public List<IEvent> GetUncommittedEvents() => 
        Events;

    public void MarkEventsAsCommitted() => 
        Events.Clear();

    public virtual void LoadFromHistory(IEnumerable<IEvent> events)
    {
        foreach (var @event in events)
            ApplyEvent(@event, false);
    }

    protected void ApplyEvent(IEvent @event, bool isNew = true)
    {
        //implementation in next parts
    }
}


 

The class contains three properties:

  • Id – identifies our unique Aggregate in the domain
  • Version – we’ll use that for checking any inconsistencies in the Event Store
  • Events – keeps events generated within the Aggregate

 

Apart from that, we can notice the LoadFromHistory method which responsibility is to reconstruct the state of domain objects based on events received from Event Store. We will come back here to implement the ApplyEvent method in one of next parts.

 

Conclusion

Knowing our domain objects, we can go further to implement other parts of the CQRS. In the next part, we’ll look more closely at the role of commands and command handlers. If you don’t want to miss all of that, I encourage you to follow me on Twitter  or leave a like on my Facebook fan page 🙂

  • Pingback: dotnetomaniak.pl()

  • Michał Wilczyński

    Provided you started introducing DDD parts to the series, it would be worth mentioning you’re actually doing that. 😉
    As for the rest, cool and to the point as always! 🙂

    • I mentioned that in a previous part but you’re right. Should do the same here. Anyway, thanks for your comment 😉

  • Łukasz Marzec

    Nice post. I’m waiting for the next part 😉

  • Mahesh

    What do you think of some existing projects out there like SimpleCQRS or CQRSLite?

    • Well, to be honest, that’s kinda weird for me. Do you really need some external projects to implement… pattern?