-
Notifications
You must be signed in to change notification settings - Fork 39
Commands
A command is a simple concept. It's an object that describes an operation to perform, along with any parameters needed to perform that particular operation.
An example could be this "Do whatever"-command:
var command = {
what: "Do whatever",
params: {
a: 23,
b: [1, 2, 3],
c: "w00t"
}
}
which is a command that happens to be represented as JSON.
When we want to model commands with C#, we can take advantage of the fact that we're in a class-oriented language, where it is common to describe classes of stuff with... classes!
Therefore, in C# we might model the "Do whatever"-command with a DoWhatever
class like this (taking full advantage of types to constrain how the command can be instantiated):
public class DoWhatever
{
public int A { get; set; }
public int[] B { get; set; }
public string C { get; set; }
}
and in order to represent the JSON command shown above, we might instantiate it like this:
var command = new DoWhatever {
A = 23,
B = new[] { 1, 2, 3 },
C = "w00t"
};
This is basically what there is to the "command" concept.
Commands are basically a chunk of data that represents a function call, but the chunk of data has the advantage that it can be serialized, saved, and transferred back and forth between processes etc.
It's easy to create a command with Cirqus: just create a command derived off of the abstract Command
class. This is the most basic command and in most situations it's easier to derive off of the abstract ExecutableCommand
class because it contains its own mapping to one or more operations in the domain (via the Execute
method implementation):
public class DoWhatever : ExecutableCommand
{
public int A { get; set; }
public int[] B { get; set; }
public string C { get; set; }
public override void Execute(ICommandContext context)
{
// do stuff in here
}
}
As you can see, you'll be forced to implement the abstract Execute
method when you create a new command - this is where you'll put some code that is executed when the command is processed.
In the Execute
method, you have access to an ICommandContext
which can be used to Create
, Load
, and TryLoad
aggregate root instances, i.e. like so:
var root = context.Load<SomeRoot>(aggregateRootId);
When you do this via the command context, an aggregate root instance will be handed to you - in this case, since we're using Load
, an exception will be thrown if the instance does not already exist.
If your command targets one specific aggregate root instance, and if you don't care whether an instance already exists, you can use the generic Command<TAggregateRoot>
type like so:
public class MyCommand : Command<SomeRoot>
{
public MyCommand(string aggregateRootId) : base(aggregateRootId) {}
public override void Execute(SomeRoot someRoot)
{
someRoot.DoStuff();
}
}
which will either load an existing instance or create it for you.
Let's take a more realistic example: a CreateNewTodoList
command:
public class CreateNewTodoList : ExecutableCommand {
public CreateNewTodoList(Guid todoListId, string title)
{
TodoListId = todoListId;
Title = title;
}
public Guid TodoListId { get; private set; }
public string Title { get; private set; }
public override void Execute(ICommandContext context) {
var todoList = context.Create<TodoList>(TodoListId.ToString());
todoList.AssignTitle(Title);
}
}
As you can see, we can use the ICommandContext
passed into the Execute
method to create and load aggregate roots by their ID - and by "load" we mean: an instance will be newed up, and all previously saved events for that aggregate root ID will be applied to the instance before it gets handed to you.
This way of executing commands is pretty generic, and you can safely load multiple aggregate roots and have them emit events, which will all be committed in the same unit of work.
Since DDD recommends that a unit of work encompasses only one single aggregate root, there's a special command base class for those scenarios: Command<TAggregateRoot>
. By deriving off of the generic command class, the CreateNewTodoList
command can be rewritten to this:
public class CreateNewTodoList : Command<TodoList> {
public CreateNewTodoList(Guid todoListId, string title)
: base(todoListId.ToString()) {
Title = title;
}
public string Title { get; private set; }
public override void Execute(TodoList todoList) {
todoList.AssignTitle(Title);
}
}
which makes it more explicit that this command addresses one single aggregate root instance. Please note that deriving off of the generic Command<SomeRoot>
will create/update as needed, depending on whether an instance already exists.
To combine multiple commands in one command, you can use the CompositeCommand
. It accepts multiple Command<T>
as parameters.
var command = new CompositeCommand(new MyFirstCommand(bla), new MySecondCommand(bla));
You can override from the abstract class Command
to create custom commands. This also requires you to use a custom mapper to map the action of the command.
public class AnotherCommand: Command
{
private readonly int _someParameter;
public AnotherCommand(int someParameter)
{
_someParameter = someParameter;
}
public void MyCustomAction()
{
// do something here...
}
}
var mappings = new CommandMappings()
.Map<SomeCommand>((context, command) => {
context.Load<SomeRoot>(command.SomeRootId).DoStuff();
})
.Map<AnotherCommand((context, command) => {
context.Load<AnotherRoot>(command.AnotherRootId).DoStuff();
});
The mappings above should be injected in the Command processor.