-
Notifications
You must be signed in to change notification settings - Fork 39
Aggregate roots
An aggregate root is an important kind of object that can work independently of other objects. It may contain other objects, and those contained objects are owned by the root. The root and everything it contains is called an aggregate. The contained objects are often called value types.
All interaction with the aggregate goes through the aggregate root, thus allowing the root to function as a facade to whatever is going on beneath it, providing a nice encapsulated way of modeling things.
This way, an aggregate can and must function as a consistency barrier, making sure that its integrity is sustained i.e. its invariants are preserved.
Creating an aggregate root with Cirqus is easy: you just create a class that inherits from AggregateRoot
, like so:
public class MyRoot : AggregateRoot { }
The AggregateRoot
base class will bring a couple of useful things with it:
- An
Id
property - since all aggregate roots must be uniquely addressable, they will have a globally unique ID. The ID is astring
which enables you to come up with suitable ID schemes for various types, e.g."customer/324"
and"case/ab678"
. - An
Emit
method - the root must only change itself by using the Emit Apply Pattern, and to do that the root must use theEmit
method. More about this in Emit Apply Pattern.
Internally it keeps track of some stuff regarding events and sequence numbers.
You create a new aggregate root instance either by calling Create<ThatRoot>("an ID")
on the ICommandContext
handed to you in a Command
or an ExecutableCommand
, simply by executing a Command<ThatRoot>
(which will create/update as needed), or, finally, you can create an instance from within another aggregate root by calling Create<ThatOtherRoot>("another ID")
on the AggregateRoot
base class.
For example like this in an ExecutableCommand
:
public class CreateNewRoot : ExecutableCommand
{
public string RootId { get; set; }
public override void Execute(ICommandContext context)
{
context.Create<Root>(RootId); //< throws an exception if the root already exists!
}
}
It should be noted that the root is considered as existent if it has emitted an event - this also means that, in this case, the only way that the context.Create
call will actually have an effect, is when the Created
method of the aggregate root has been overridden to emit an event.
It's not possible to delete an instance. To achieve this, we recommend using soft deletes. You keep track of the instance's state using a boolean or a nullable property and you update this property when the instance should be deleted.
public class MyRoot: AggregateRoot
{
private bool _isDeleted;
public bool IsDeleted { get { return _isDeleted; } }
public void Apply(MyDeleteEvent deleteEvent)
{
_isDeleted = true;
}
public void Delete()
{
Emit(new MyDeleteEvent());
}
}
You could also use something like DateTimeOffset?
so that you can keep track of when the instance was deleted.
Make sure to read Emit Apply Pattern to get a grasp of how you can update a property or field of an aggregate root.