Click here to Skip to main content
Click here to Skip to main content

Griffin.Container: Introducing the command support.

, 22 Jun 2012
Rate this:
Please Sign up or sign in to vote.
Commands are a great way to decouple logic in an application, since the command invoker have no knowledge of where the command is handled. You also move the focus from the how the task is solved to the task itself.

Commands are a great way to decouple logic in an application, since the command invoker have no knowledge of where the command is handled. You also move the focus from the how the task is solved to the task itself.

A command which is used together with Griffin.Container can look like this:

public class CreateUser : ICommand
{
	public CreateUser(string firstName, string lastName)
	{
		if (firstName == null) throw new ArgumentNullException("firstName");
		if (lastName == null) throw new ArgumentNullException("lastName");

		FirstName = firstName;
		LastName = lastName;
	}

	public string FirstName { get; private set; }
	public string LastName { get; private set; }
}

Notice that the commands are immutable and do not return a value. A command should always validate all entered information (always catch errors as early as possible). Mandatory fields should be specified in the constructor while optional is set through methods or properties.

That is important if you want to be able to scale your application later on. You can, for instance, at any time serialize the command and send it to another server. Thanks to that the command is immutable and a do not return value is expected. I’ll demonstate how you can achieve that using decorators and Griffin.Networking in a later blog post.

Let’s focus on the command now. We need to dispatch it in some way so that it can be invoked.

Here is an example ASP.NET MVC3 controller:

public class UserController : Controller
{
	ICommandDispatcher _dispatcher;

	public UserController(ICommandDispatcher dispatcher)
	{
		_dispatcher = dispatcher;
	}

	[HttpPost, Transactional]
	public ActionResult Create(CreateModel model)
	{
		if (!ModelState.IsValid)
			return View(model);

		var cmd = new CreateCommand(model.FirstName, model.LastName);
		_dispatcher.Dispatch(cmd);

		return RedirectToAction("List");
	}
}

We do not know where the command is invoked and we should really not care either. We could even go a step further and not care about when the command is executed (as long as we have some kind of notification system to inform the user of failed commands). It all depends on how scalable your application should be (whether you consider that for now or later).

We could start with an application where everything is synchronous and executed in the same process and end up with a application where the commands are distributed over several servers.

Getting a Result

Commands should not return a result. It complicates the command handling and forces everything to be synchronous (or even more complex). It’s of course a trade off, but it also makes everything more flexible.

You still have the information which you used to invoke the command. You can use that information to update the UI to fake that a new item has been created/updated (which it will eventually). You could also inform the user that the item has been handled and that he needs to refresh the UI to see it.

If you need an ID, switch to GUIDs and generate and attach it to the command before invoking it. There are GUIDs which works well with databases.

Handling the command

Commands are handled by classes which implements the interface IHandlerOf<T>.

A typical implementation looks something like this:

[Component]
public class CreateUserHandler : IHandlerOf<CreateUser>
{
	private IUserRepository _repository;

	public CreateUserHandler(IUserRepository repository)
	{
		_repository = repository;
	}

	public void Invoke(CreateUser cmd)
	{
		var user = _repository.Create(cmd.FirstName, cmd.LastName);

		DomainEvent.Publish(new UserCreated(user));
	}
}

The [Component] attribute will allow you to automatically register the class in the container. Read more about that in the core documentation

Nesting Commands

No. Do not nest commands. A command is created for a specific use case. It’s not intended to get reused. Nesting commands can quickly produce complex code which is hard to follow and maintain.

Instead break out common functionality to a third class which is used by both commands.

Decorators

Decorators allows you to add functionality to the command handlers without actually having to modify them.

You could for instance create an decorator which meassure the performance of every command. It would look something like this:

public class PerformanceMonitor<T> : IHandlerOf<T> where T : class, ICommand
{
    private readonly IHandlerOf<T> _inner;

    public PerformanceMonitor(IHandlerOf<T> inner)
    {
        _inner = inner;
    }

    public void Invoke(T command)
    {
        var w = new Stopwatch();
        w.Start();
        _inner.Invoke(command);
        w.Stop();
        Console.WriteLine("Invocation of {0} took {1}ms.", command.GetType().Name, w.ElapsedMilliseconds);
    }
}

All decorators are attached the the handlers by using factories. A factory which would attach the performance decorator to all commands would look something like:

[Component]
public class ExceptionDecoratorFactory : IDecoratorFactory
{
	// used to determine which commands to decorate
    public bool CanDecorate(Type commandType)
    {
        return true;
    }

	// invoked if we can decorate a command
    public IHandlerOf<T> Create(IHandlerOf<T> inner) where T : class, ICommand
    {
        return new PerformanceMonitor(inner);
    }
}

The factory itself should be added to the container. The command dispatcher automatically finds all factories which have been registered.

Exception decorator

A decorator called ExceptionDecorator<T> is included in the framework. It automatically logs all failed commands and their properties.

Example output:

FailureCommand
    Parameters:
        FirstName: Arne
    Exception:
        System.InvalidOperationException: That wont work, dude!
           at Griffin.Container.Tests.Commands.DecoratorTests.FailureHandler.Invoke(FailureCommand command) in C:\projects\csharp\gauffin\Griffin.Container\Source\Griffin.Container.Tests\Commands\DecoratorTests.cs:line 53
           at Griffin.Container.Tests.Commands.DecoratorTests.PerformanceMonitor`1.Invoke(T command) in C:\projects\csharp\gauffin\Griffin.Container\Source\Griffin.Container.Tests\Commands\DecoratorTests.cs:line 39
           at Griffin.Container.Commands.ExceptionDecorator`1.Invoke(T command) in C:\projects\csharp\gauffin\Griffin.Container\Source\Griffin.Container\Commands\ExceptionDecorator.cs:line 39

Validation decorator

There is also a validation decorator included that is used to validate properties on the commands. It uses DataAnnotation attributes for the validation.

Example command (required parameters are manually validated in the constructor while optional properties is validated using DataAnnotations):

public class CreateUser : ICommand
{
	public CreateUser(string firstName, string lastName)
	{
		if (firstName == null) throw new ArgumentNullException("firstName");
		if (lastName == null) throw new ArgumentNullException("lastName");

		FirstName = firstName;
		LastName = lastName;
	}

	public string FirstName { get; private set; }
	public string LastName { get; private set; }

	[StringLength(40)]
	public string Title { get; set; }
}

Getting the Code

The code is available at github and is included in the main project.

You can get it by using nuget: install-package griffin.container.

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)

About the Author

jgauffin
Founder Gauffin Interactive AB
Sweden Sweden
Founder of OneTrueError, a .NET service which captures, analyzes and provide possible solutions for exceptions.
 
blog | twitter
Follow on   Twitter   LinkedIn

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Mobile
Web03 | 2.8.140709.1 | Last Updated 22 Jun 2012
Article Copyright 2012 by jgauffin
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid