Microservices Re-architecture Part 6: Practical CQRS - Commands

Tags: microservices, Dependency Injection

In my last microservices post I talked about how we are using CQRS in our microservices at Centerfield Media. That post focused on the queries; this post is about commands.

Commands perform some action like creating a business object. Commands are not supposed to return any data. Mine do, and I'll explain why in a bit.

Command Handler Interface

This is the interface all command handlers inherit from: 


public interface ICommandHandler<TCommand> 
{ 
Task HandleAsync(TCommand command);
}


Pretty simple, it just defines a method to perform the action of the command.

Base Command

For one of our microservices I created and BaseCommand class that defines any objects saved or deleted. It looks like this:

    
        public class BaseCommand
	{

		private IList entitiesSaved = new List();

		private IList entitiesDeleted = new List();

		/// 
		/// The state of the entity prior to editing, if this is populated auditing log entry will be added
		/// 
		public ICenterfieldData OriginalEntity { get; set; }

		/// 
		/// name of user executing this command
		/// 
		public string User { get; set; }

		
		/// 
		///  Data entities added or updated as a result of this command.
		///  A decorator will sync entities in this list to MSSQL
		/// 
		public IList EntitiesSaved
		{
			get
			{
				return entitiesSaved;
			}

			set
			{
				entitiesSaved = value;
			}
		}

		/// 
		/// Data entities deleted as a result of this command
		/// A decorator will sync entities in this list to MSSQL
		/// 
		public IList EntitiesDeleted
		{
			get
			{
				return entitiesDeleted;
			}

			set
			{
				entitiesDeleted = value;
			}
		}
	}

I need to return data from my commands to later decorators will later do something with that data.  Some things my decorators so:

  1. Entities saved and deleted will be sent to queue for sync up to a legacy database (MSSQL)
  2. Original state of the entity will be logged to an audit log.
  3. Cache will be refreshed with latest data

Example Command

A command is just the input to the command handler, in most cases ,its just the item that will be saved.   An example command may look like so:

  
public class SaveOfferCommand : BaseCommand
{
       public Offer Offer { get; set; } 
} 

In the command handler is where all the action happens.  And an example command handler may look like so:

       public class SaveOfferCommandHandler : CommandHandlerBase , ITransactionCommandHandler
    {
        public SaveOfferCommandHandler(IManagementMySqlRepositoryReadWrite managementMySqlRepository)
            : base(managementMySqlRepository) { }

        public override async Task HandleAsync(SaveOfferCommand command)
        {
                if (command == null) throw new ArgumentNullException("SaveOfferCommand");
                if (command.Offer == null) throw new ArgumentNullException("Offer");
                if (command.Offer.OfferID == 0) throw new ArgumentException("OfferID");

		var originalEntity = _managementMySqlRepository.Get().Where(o => o.OfferID == command.Offer.OfferID).FirstOrDefault();            
		command.OriginalEntity = originalEntity;

                ....do the save...

		command.EntitiesSaved.Add(savedOffer);
			
            return;
        }
}

 

Using a DI Container

SimpleInjector is a dependency injection container that can manage all these commands and queries simply and gracefully. You can dump all the commands and queries into the container with only a couple of lines of code. Power! Here's what is looks like:


//register all CommandHandlers 
container.Register(typeof(ICommandHandler<>), new[] { typeof(CommandHandlerBase<>).Assembly });

//register all QueryHandlers
container.Register(typeof(IQueryHandler<,>), new[] { typeof(QueryHandlerBase<,>).Assembly });


The first line adds all the command handlers into the container, and the seconds adds all the query handlers. As you add new commands and queries to your system they will be automatically added to the container. No more thinking about DI!

The other awesome thing about SimpleInjector is that it will wrap the decorators (discussed in the previous post) for you in one line of code!  Here's an example of adding a decorator around every query handler:


container.RegisterDecorator(typeof(IQueryHandler<,>), typeof(ErrorLoggerQueryHandlerDecorator<,>)); 

And here's a decorator added to every command handler:


container.RegisterDecorator(typeof(ICommandHandler<>), typeof(AuditingCommandHandlerDecorator<>)); 

This is powerful stuff!

Wrap Up

This style of CQRS combined with SimpleInjector is a powerful combination that drives your code to follow good coding practices (SOLID principles) such as small classes, thin interfaces, reusable cross cutting code, dependency inversion, and extensive use of reusable types.

Read more on .Net Junkie's blog!

No Comments

Add a Comment