Description
Even though I know this should be seen as a demonstration for learning CQRS and is just the most simplest implementation... I have seen people jump onto this horse and start to ride. Because of this I wan't to take a note on the implementation of Command
, Event
, FakeBus
and the registration of a handler.
Both Command
and Event
share the same base class Message
here. I don't think you should go this path, especially when looking at the registration.
There are two different contracts for Command registration and Event registration.
- You can publish to multiple (Event)handlers,
- but you are not allowed to send a Command to multiple (Command)handlers, there can be only one.
You will notice this here in the FakeBus
implementation:
public void Send<T>(T command) where T : Command
{
List<Action<Message>> handlers;
if (_routes.TryGetValue(typeof(T), out handlers))
{
if (handlers.Count != 1) // look at this, only 1 handler allowed
throw new InvalidOperationException("cannot send to more than one handler");
handlers[0](command);
}
else
{
throw new InvalidOperationException("no handler registered");
}
}
... on the other hand the Publish method does not have such a restriction.
But using the RegisterHandler<T>(Action<T> handler) where T : Message
method you were already able to register as much as Command
handlers for the same Command
as you want:
public void RegisterHandler<T>(Action<T> handler) where T : Message
{
List<Action<Message>> handlers;
if(!_routes.TryGetValue(typeof(T), out handlers))
{
handlers = new List<Action<Message>>();
_routes.Add(typeof(T), handlers);
}
// no rules applied to check and fail early when trying to register 2nd Command handler
handlers.Add((x => handler((T)x)));
}
Because of this, the RegisterHandler<T>(Action<T> handler) where T : Message
should be really something like RegisterEventHandler<T>(Action<T> handler) where T : Event
and RegisterCommandHandler<T>(Action<T> handler) where T : Command
.
This way you are able to express and check the required rules earlier in the registration process (e.g. throw Exception
during startup instead of running a long time with a hidden bug).
I would not even make Command
and Event
inherit from a shared base class at this point (any reasons to give?). The implemented FakeBus
itself might be capable of transport both and might be the same instance. Greg already created two different interfaces, ICommandSender
and IEventPublisher
so you are able to keep them in separate implementations that wrap a common transport bus.