A Trip to CQRS – Commands 2

This is part 2 of 5. The entire series is here:

    1. Intro
    2. Commands
    3. Events
    4. Authoritative Events
    5. New Views

This is post #2 in the series. I very much recommend you to read them in order, otherwise I’m pretty sure confusion will arise.. All the code for this step is found on the commands branch:

https://github.com/andlju/hotel-admin/tree/commands

Commands and Command Handlers

Anyway, this is when we actually do something useful. Let’s introduce Commands!

CQRS Commands

A Command is simply a DTO-type of object that contains the data necessary for the service to make one specific change to the state of the system. As an example, the AddHotelCommand looks like this:

The commands are pushed to a simple Dispatcher who passes them on to an appropriate Command Handler:

No we need to make sure that the old Transaction Script methods are updated to publish commands instead of using repositories themselves. This is when we run into the first issue.

Have a look at the signature of the Remote Façade:

Spot the problem? Yes, it is supposed to return the id of the newly created Hotel, otherwise the UI will be in trouble. But we have already stated that Commands are not allowed to return stuff, so what to do?

Identities

The common answer to that problem when it arises in a CQRS system is to use Guids as identifiers. That way the client (or whoever sends the Command) can be the one creating the identifier and does not have to wait for the server to respond before continuing to work on the object.

We pretty much have two options here. Remodel the database to use Guids instead of auto-incremented Ids, or introduce some kind of mapping between the two. Since we’ve said from the start that the DB should remain as intact as possible, mapping had to be the solution of choice for us.

If you look at the end of the AddHotelCommandHandler above, you’ll see that the last thing that happens is a call to _identityMapper.Map. This will insert the mapping in a simple lookup table. The AddHotel method in the service can now use this map in order to return the Id:

If you’re paying attention, this will be the place where you say something like: “But, but. This means that all Command Handlers must be synchronous. And maybe even inside a.. a.. a Transaction??”. And you’ll be completely correct. But notice how “improved performance/scalability” was never one of the goals for this refactoring. I may revisit this though, so hang tight through the next couple of posts..

Testing

Testing the Commands are pretty similar to testing the old Service. We can put a little bit more thought into the base fixture though, since we know that the “When” will always be a Command, and the “Given” will be a Command Handler:


Benefits

Now I did promise that we would gain at least something in each step, didn’t I? Well, this may be a bit of a long-shot, but at least we have introduced a bit of Single Responsibility/Separation of Concerns to our architecture. The Command Handlers only have to inject the dependencies they actually need (as opposed to the old Service which is likely to become quite bloated). On the other hand, things did get a little more complex by introducing the Dispatcher, not to mention the Identity Map thingy…

You’d better keep reading the next post. Things may make a little bit more sense after that one.

2 thoughts on “A Trip to CQRS – Commands

  1. Ben Nov 13,2012 11:31

    I still wouldn’t use GUIDs … and what if the command failed and teh GUI is happily running allong with its new Entity with GUID.. With CQRS there is no choice you are going to have to send events to the GUI ( from the read model)

    • anders Nov 13,2012 16:16

      I agree that for a “proper” CQRS solution with all bells and whistles (async read model etc, etc) sending events to the GUI is a way better idea (there are other choices though, but they’re not as good).

      In this case though, since I’m doing everything synchronously and even in a transaction – if the command fails, the GUI will know!

Comments are closed.