Skip to content

A Beginner's Guide To Implementing CQRS ES Part 3: The Commands Interface

cdmdotnet edited this page Feb 14, 2018 · 13 revisions

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

Building from the work done in Part 1 and Part 2, we are now ready to implement a front-end to our CQRS/ES system. For this demo, the front end will be a console application, and to keep things extremely simple we won't be using any container to provide Dependency Injection, instead we'll be using a sample runtime specifically for doing proof of concept projects.

Defining and Using the Runtime

The first thing we need to do is create an instance of the sample runtime in which our CQRS/ES app will execute. That runtime will save eventstores and datastores to a SQL server. For smaller, light-weight proof of concepts you can use an in-memory eventstore. The runtime is started within the Main() method of the console app:

using Cqrs.Configuration;

static class Program
{
	public static void Main()
	{
		using (new SqlSampleRuntime<string, CreateMovieCommandHandler>())
		{
		}
	}
}

The runtime will automatically wire-up all your event handlers and command handlers. This is achieved by passing in a type reference to just one handler, in this case the CreateMovieCommandHandler. The runtime will automatically look for all handlers in the assembly and wire them up for you.

With this runtime in place, we are ready to add a little logic:

using System;
using Cqrs.Commands;
using Cqrs.Configuration;

static class Program
{
	public static void Main()
	{
		using (new SqlSampleRuntime<string, CreateMovieCommandHandler>())
		{
			string command = "Help";
			while (HandleUserEntry(command))
			{
				command = Console.ReadLine();
			}
		}
	}

	private static bool HandleUserEntry(string text)
	{
		switch (text.ToLowerInvariant())
		{
			case "help":
				Console.WriteLine("Add Movie");
				Console.WriteLine("\tWill allow you to add a new movie.");
				Console.WriteLine("Quit");
				Console.WriteLine("\tWill exit the running program.");
				Console.WriteLine("Help");
				Console.WriteLine("\tWill display this help menu.");
				break;
			case "add movie":
			case "addmovie":
				Console.WriteLine("Enter the title for a new movie.");
				string title = Console.ReadLine();

				int duration;
				do
				{
					Console.WriteLine("Enter the duration (how long) in minutes, the new movie runs for.");
					string durationValue = Console.ReadLine();
					if (int.TryParse(durationValue, out duration))
						break;
					Console.WriteLine("The entered value of {0} was not a whole number.", durationValue);
				} while (true);

				var commandPublisher = DependencyResolver.Current.Resolve<ICommandPublisher<string>>();
				commandPublisher.Publish(new CreateMovieCommand(title, DateTime.Now, duration));

				break;
			case "quit":
			case "exit":
				return false;
		}
		return true;
	}
}

This simple console application presents a help menu, allows you to add a new movie asking for the name and duration of a movie to add to the database and to exit the program. Looking at the Add Movie operation, the beginning collects information from the 'user' about the movie to add. We can skip over this logic as it's just sample code and go straight to the final two lines of code where we obtain a reference to the command publisher in-order to issue the command, this is done with the Publish() method. The final line is a single call to that Publish() method providing it with the command we created previously. Those two lines of code is now all that is needed to have a fully-functional CQRS system!

Finally to run the console application there are a few updates to make to your app.config file:

<configSections>
	<section name="entityFramework"
		type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
		requirePermission="false"/>
	<section name="LoggerSettings" type="cdmdotnet.Logging.Configuration.LoggerSettingsConfigurationSection, cdmdotnet.Logging"/>
</configSections>
<LoggerSettings EnableInfo="true" EnableProgress="true" EnableSensitive="false" EnableDebug="false" EnableWarning="true" EnableError="true"
	EnableFatalError="true" EnableThreadedLogging="true" ModuleName="MyCompany" Instance="MyApplication" EnvironmentInstance="Server1" Environment="Production"
	EnableThreadedLoggingOutput="true" UseApplicationInsightTelemetryHelper="false" UsePerformanceCounters="false"
	SqlDatabaseLogsConnectionStringName="SqlDatabaseLogs.Local" SqlDatabaseTableName="Logs"/>

For now we don't log to SQL server, so don't worry about that for now. Also add a connection string making any modifications necessary for your local machine.

<connectionStrings>
	<add name="MoviesDataStore"
		connectionString="Data Source=localhost;Initial Catalog=Movies;Integrated Security=True;MultipleActiveResultSets=True;App=Sample CQRS Movie App."
		providerName="System.Data.SqlClient"/>
	<add name="EventStore"
		connectionString="Data Source=localhost;Initial Catalog=Movies;Integrated Security=True;MultipleActiveResultSets=True;Connect Timeout=30;Application Name=Sample CQRS Movie App Event Store;"
		providerName="System.Data.SqlClient"/>
</connectionStrings>

And finally create a table in your SQL server for your Movie datastore as such:

CREATE TABLE [dbo].[Movies](
	[Rsn] [uniqueidentifier] NOT NULL,
	[IsLogicallyDeleted] [bit] NOT NULL,
	[SortingOrder] [int] NOT NULL,
	[Title] [nvarchar](200) NOT NULL,
	[ReleaseDate] [datetime] NOT NULL,
	[RunningTimeMinutes] [int] NOT NULL,
PRIMARY KEY CLUSTERED 
(
	[Rsn] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

ALTER TABLE [dbo].[Movies] ADD  DEFAULT ((0)) FOR [IsLogicallyDeleted]
GO

ALTER TABLE [dbo].[Movies] ADD  CONSTRAINT [DF_Movies_SortingOrder]  DEFAULT ((0)) FOR [SortingOrder]
GO

Locate the file EventStoreTable-SqlServer.sql in the tools folder of your project and execute that on your database as well.

User Friendly Interface

There is only one final step to make this console app a little more helpful though... something that isn't A CQRS based concern, just good user feedback... letting the user know the operation has completed. We'll do this in a CQRS manner though utilising a second Event Handler as follows:

using System;
using Cqrs.Events;

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

Now we have a complete feedback cycle using CQRS.

Order of Execution

At a high level, the order of execution when you publish a command is:

  1. The CreateMovieCommand is placed on the Command Bus.
  2. The CreateMovieCommandHandler picks up the CreateMovieCommand and processes it.
  3. As part of the Handle() method, a new Aggregate Root is created and added to the UnitOfWork.
  4. The CreateMovieCommand is passed to the Create() method raising a MovieCreatedEvent.
  5. The Commit() method requests the EventStore saves the MovieCreatedEvent.
  6. The EventStore first verifies the Aggregate Root has not been modified since it was loaded,
  7. The EventStore then serialises the raised MovieCreatedEvent, saves it to the EventStore and then publishes it on the EventBus.
  8. All Event Handlers which have subscribed (are listening for) a MovieCreatedEvent are delivered the event and processes it.
  9. In our specific case, the MovieCreatedEventHandler saves the current state to the DataStore.

Querying

The one part we've not covered yet is querying the datastore. One of the main benefits of CQRS is that you can store your eventstore (used for your command handlers) and datastores (used for your queries) on different servers. Introducing a heavy workload on one will not affect the other. Any outage of your eventstore may mean you can't make changes to your data, but you can still read from you data store. Logging in and displaying dashboards etc. will all still be possible if you were to suffer such an outage. Infact, you can use read-only clones of your datastores (a feature built into SQLServer and Azure) so that even if you suffered a major outage, if at-least just one of your clone datastores is available... your application is still available and online. A very basic implementation might look like this:

using System;
using System.Collections.Generic;
using System.Data.Linq;
using System.Linq;
using Cqrs.Commands;
using Cqrs.Configuration;

static class Program
{
	private static IDictionary<int, MovieEntity> Movies { get; set; }

	public static void Main()
	{
		using (new SqlSampleRuntime<string, CreateMovieCommandHandler>())
		{
			string command = "Help";
			while (HandleUserEntry(command))
			{
				command = Console.ReadLine();
			}
		}
	}

	private static bool HandleUserEntry(string text)
	{
		switch (text.ToLowerInvariant())
		{
			case "help":
				Console.WriteLine("Add Movie");
				Console.WriteLine("\tWill allow you to add a new movie.");
				Console.WriteLine("Get All Movies");
				Console.WriteLine("\tWill get a list of all movies.");
				Console.WriteLine("Quit");
				Console.WriteLine("\tWill exit the running program.");
				Console.WriteLine("Help");
				Console.WriteLine("\tWill display this help menu.");
				break;
			case "add movie":
			case "addmovie":
				Console.WriteLine("Enter the title for a new movie.");
				string title = Console.ReadLine();

				int duration;
				do
				{
					Console.WriteLine("Enter the duration (how long) in minutes, the new movie runs for.");
					string durationValue = Console.ReadLine();
					if (int.TryParse(durationValue, out duration))
						break;
					Console.WriteLine("The entered value of {0} was not a whole number.", durationValue);
				} while (true);

				var commandPublisher = DependencyResolver.Current.Resolve<ICommandPublisher<string>>();
				commandPublisher.Publish(new CreateMovieCommand(title, DateTime.Now, duration));

				break;
			case "get all movies":
			case "list all movies":
			case "getallmovies":
			case "listallmovies":
				using (DataContext dbDataContext = new DataContext(System.Configuration.ConfigurationManager.ConnectionStrings["MoviesDataStore"].ConnectionString))
				{
					int index = 1;
					Movies = dbDataContext
						.GetTable<MovieEntity>()
						.ToDictionary(movie => index++);

					foreach (var movie in Movies)
					{
						Console.WriteLine("{0:N0}) {1}", movie.Key, movie.Value.Title);
						Console.WriteLine("\t{0:N0} minute{1} long.", movie.Value.RunningTimeMinutes, movie.Value.RunningTimeMinutes == 1 ? null : "s");
					}

					Console.WriteLine("A total of {0} movie{1}", Movies.Count, Movies.Count == 1 ? null : "s");
				}
				break;
			case "quit":
			case "exit":
				return false;
		}
		return true;
	}
}

There you have it! We've got a fully-functional CQRS/ES application, with a console application front end, and a full event store. In the next and final part of this series we'll add some more events and commands, specifically ones that implement changes being made to an already-existing Aggregate Root, as well as list some drawbacks of this particular solution.

Clone this wiki locally