Skip to content

A Beginner's Guide To Implementing CQRS ES Part 5: Informative thinking

cdmdotnet edited this page Feb 18, 2018 · 5 revisions

This is Part 5 of a 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]. Click here for Part 1.

We've already dealt with commands and events that create new and update existing Aggregate Roots in Part 2 and Part 4 of this series. Now we can look at more informative events.

Adding Further Information to Update Events

Let's imagine that we want to know if the title was actually changed or not, to do so we need a more informative MovieTitleUpdatedEvent that looks like this:

using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using Cqrs.Domain;
using Cqrs.Events;
using Cqrs.Messages;

public class MovieTitleUpdatedEvent : IEventWithIdentity<string>
{
	#region Implementation of IEvent

	/// <summary>
	/// The Id of the <see cref="IEvent{TAuthenticationToken}" /> itself, not the object that was created. This helps identify one event from another if two are sent with the same information.
	/// </summary>
	[DataMember]
	public Guid Id { get; set; }

	/// <summary>
	/// The version number the <see cref="AggregateRoot{TAuthenticationToken}">aggregate root</see> shifted to as a result of the request.
	/// </summary>
	[DataMember]
	public int Version { get; set; }

	/// <summary>
	/// The time the event was generated. Application of the event may happen at a different time.
	/// </summary>
	[DataMember]
	public DateTimeOffset TimeStamp { get; set; }

	#endregion

	#region Implementation of IMessageWithAuthenticationToken<Guid>

	/// <summary>
	/// The authentication token used to identify the requester.
	/// </summary>
	[DataMember]
	public string AuthenticationToken { get; set; }

	#endregion

	#region Implementation of IMessage

	/// <summary>
	/// The correlation id used to group together events and notifications.
	/// </summary>
	[DataMember]
	public Guid CorrelationId { get; set; }

	/// <summary>
	/// The originating framework this message was sent from.
	/// </summary>
	[DataMember]
	public string OriginatingFramework { get; set; }

	/// <summary>
	/// The frameworks this <see cref="IMessage"/> has been delivered to/sent via already.
	/// </summary>
	[DataMember]
	public IEnumerable<string> Frameworks { get; set; }

	#endregion

	#region Implementation of IEventWithIdentity

	/// <summary>
	/// The Rsn of the <see cref="AggregateRoot{TAuthenticationToken}">aggregate root</see> create.
	/// </summary>
	[DataMember]
	public Guid Rsn { get; set; }

	#endregion

	[DataMember]
	public string OriginalTitle { get; set; }

	[DataMember]
	public string Title { get; set; }

	public MovieTitleUpdatedEvent(Guid rsn, string originalTitle, string title)
	{
		Rsn = rsn;
		OriginalTitle = originalTitle;
		Title = title;
	}
}

Updating the Update Call

This doesn't require us changing the UpdateMovieTitleCommandHandler, only the Movie Aggregate Root. Specifically we need to keep track, in the current state, the current value of the Title. Let's see the new Movie Aggregate Root class:

using System;
using cdmdotnet.Logging;
using Cqrs.Configuration;
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 string Title { get; private set; }

	/// <summary>
	/// Instantiates a new instance of a new movie.
	/// </summary>
	public Movie(Guid rsn)
	{
		Rsn = rsn;
	}

	/// <summary>
	/// Instantiates a new instance of an existing movie.
	/// </summary>
	public Movie(IDependencyResolver dependencyResolver, ILogger logger, Guid rsn)
	{
		Rsn = rsn;
	}

	public void Create(string title, DateTime releaseDate, int runningTimeMinutes)
	{
		MovieCreatedEvent movieCreatedEvent = new MovieCreatedEvent(Rsn, title, releaseDate, runningTimeMinutes);
		ApplyChange(movieCreatedEvent);
	}

	public void UpdateTitle(string title)
	{
		MovieTitleUpdatedEvent movieTitleUpdatedEvent = new MovieTitleUpdatedEvent(Rsn, Title, title);
		ApplyChange(movieTitleUpdatedEvent);
	}
}

The two changes here are the Title property, holding the current value (state) of the movies title, and passing this current (originally state pre-update) value of the Title.

Of course, this asks the question of how do we apply the current value to the Title property? This is done via an Apply method:

using System;
using cdmdotnet.Logging;
using Cqrs.Configuration;
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 string Title { get; private set; }

	/// <summary>
	/// Instantiates a new instance of a new movie.
	/// </summary>
	public Movie(Guid rsn)
	{
		Rsn = rsn;
	}

	/// <summary>
	/// Instantiates a new instance of an existing movie.
	/// </summary>
	public Movie(IDependencyResolver dependencyResolver, ILogger logger, Guid rsn)
	{
		Rsn = rsn;
	}

	public void Create(string title, DateTime releaseDate, int runningTimeMinutes)
	{
		MovieCreatedEvent movieCreatedEvent = new MovieCreatedEvent(Rsn, title, releaseDate, runningTimeMinutes);
		ApplyChange(movieCreatedEvent);
	}

	public void UpdateTitle(string title)
	{
		MovieTitleUpdatedEvent movieTitleUpdatedEvent = new MovieTitleUpdatedEvent(Rsn, Title, title);
		ApplyChange(movieTitleUpdatedEvent);
	}

	private void Apply(MovieCreatedEvent movieCreatedEvent)
	{
		Title = movieCreatedEvent.Title;
	}

	private void Apply(MovieTitleUpdatedEvent movieTitleUpdatedEvent)
	{
		Title = movieTitleUpdatedEvent.Title;
	}
}

The Apply() methods (One for each event that specify a Title value) are called automatically, first, by the ApplyChange method, so the new state is immediately applied to any relevant properties, and each time, automatically, in an operation called re-hydration. Re-hydration is operation that replays all the changes/events, to the Aggregate Root by the UnitOfWork when you call the UnitOfWork.Get() method back in the command handler.

Because we now have more informative events, we can improve our sample application friendly user interface:

using System;
using Cqrs.Events;

public class SendConsoleFeedbackEventHandler
	: IEventHandler<string, MovieCreatedEvent>
	, IEventHandler<string, MovieTitleUpdatedEvent>
{
	public void Handle(MovieCreatedEvent createdEvent)
	{
		Console.WriteLine("A new movie was added with the title {0}.", createdEvent.Title);
	}

	public void Handle(MovieTitleUpdatedEvent updatedEvent)
	{
		Console.WriteLine("The movie {0} was renamed to {1}.", updatedEvent.OriginalTitle, updatedEvent.Title);
	}
}

An Important Note

It is important to note that we didn't change the name of the property Title on the event to something like NewTitle. The reason for this is that all your previously stored event in your EventStore wouldn't have a property called NewTitle so during re-hydration there would be no information to set. This choice not to rename the event property is also the reason we don't have to update the UpdateMovieEntityEventHandler class. This leeds specifically into one of the more advanced concepts of an event sourced application... event versioning. That is a topic we'll leave for a later date as there are multiple ways to handle that.

The next article takes a deeper step, moving from the console to the web.

Clone this wiki locally