This events will be fetched by AR2 and then processed.
Ohh, that sounds like a bad idea.
One more notice - in AR2 this layer would be read-only = needed to do some business logic inside AR2.
So looking at your picture, AR2 is writing out events (d,e,f), which says that AR2 is not read only -- which is good; read only aggregate roots don't make sense.
The usual pattern for what you seem to be trying to do here is to use a process manager to coordinate the activities of the two aggregate roots. The role of the process manager is to listen for events, and respond by dispatching commands.
In that picture, you would have something like the following:
Command(A) arrives at AR(1)
AR(1) loads its history [x,y,z]
AR(1) executes Command(A), producing events [a,b,c]
AR(1) writes its new history [x,y,z,a,b,c]
Events(a,b,c) are published
ProcessManager receives the events (a,b,c)
ProcessManager dispatches Command(B) to AR(2)
Command(B) arrives at AR(2)
AR(2) loads its own history [d,e,f]
AR(2) executes Command(B), producing events [g,h]
AR(2) writes its new history [d,e,f,g,h]
Events(g,h) are published
Trying to have two different aggregate roots share a common event history is really weird; it strongly suggests that your model needs rethinking (why are there two different authorities for the same fact? what happens when AR1 writes an event that violates the invariant enforced by AR2?).
But taking some of the state from one event, and making that an argument in a command sent to another aggregate; that pattern is pretty common. The process manager itself is just a substitute for a human being reading the events and deciding what commands to fire.
I always need current state of AR1 layer, even if AR2 is created from the scratch it needs to have content of layer from AR1. The layer would be read only.
There's no such thing as getting the "current" state of another aggregate; AR1 could be changing while AR2 is doing its work, and there's no way to know that. If that's not acceptable, then your aggregate boundaries are in the wrong place.
If stale data is acceptable, you can have the AR2 command handler query the state of AR1, and use that information in processing the command. If you are going to do that, I normally prefer to wrap the query in a Domain Service, which gives you an extra layer of indirection to work with (the domain model doesn't need to know how the service is implemented). In this design, AR2 doesn't see the AR1 events at all; AR2 passes some state to the domain service, and the domain service looks at the events to figure out the answer, and passes that answer back as a value that AR2 will understand.
Whittaker's solution isn't bad; once you recognize that the data is stale anyway, you have the option of deciding whether the state available at the time of creating the command is good enough. I'm of mixed minds on this -- putting everything into the command is nice, and really easy to understand. On the other hand, there is a larger window for a change to happen, and to some degree discovering the right data to use requires accessing state internal to the aggregate that can change while the command is in flight.
I much prefer designs where the aggregates aren't coupled, though.
But it seems that this is again sharing of data between the AR-s, since fat command will use data from layer from AR1 to supply to AR2
You might look into what Udi Dahan has to say about services as technical authorities. In that case, the data that gets shared is mostly limited to opaque identifiers.
If you want to do it the right way as DDD suggests, then you are only allowed to change shape colour by using a method on the aggregate root directly and you cannot do it on the shape directly. The aggregate root is only allowed to expose its internal entities as read-only objects (e.g. exposing internal collections as IEnumerable).
However, good aggregate design is also a simple design and when you come to a situation in which your aggregate root grows, the first question you should ask yourself is: Which context does the aggregate actually provide for the entities?
In your case, you should think whether the Shape
really is just an internal entity, or whether the Shape
could be an aggregate root on its own?
Your Shape
class should remain an only internal entity of the Picture
aggregate root when the Picture
defines some rules among multiple Shape
instances, such as: A picture may not contain two shapes of the same color. Having defined this rule, the aggregate root - knowing about all of its shapes - can now verify this rule.
But, if the Picture
aggregate root does not really define any rule as such and only internally stores the Shape
s then the Shape
really should be an aggregate root on its own with the following structure:
class Shape
{
private ShapeId shapeId;
private PictureId associatedPictureId;
private ShapeType type;
private Color color;
}
and the Picture
would have the following method:
class Picture
{
private PictureId pictureId;
public Shape AddShape(ShapeId shapeId, ShapeType shapeType, Color color)
{
return new Shape(shapeId, pictureId, shapeType, color);
}
}
Always think of an aggregate root as a protector of boundaries which the aggregate root defines for its internal parts. If it does not define any boundaries and rules for its internal parts, split the internals into separate aggregate roots to remove complexity.
Best Answer
I think that option no. 2 is the solution, with a small but important modification:
AR1
should not emit a event who's purpose is to create theAR2
, instead it should emit aAR1WasCreated
event. This event should be persisted in the event store, as it is an important event marking the birth ofAR1
. Then, aSaga
whould listent forAR1WasCreated
event and generate a command to createAR2
:CreateAR2Command
.Option no.1 is very wrong. You should never inject that kind of domain service in an
Aggregate
.Aggregates
should be pure, with no side effects other that the generation of events.P.S. I never emit events from the constructor of the
Aggregate
as there is a distinction between creating an instance of an object (in the programming language sense) and the creation (the birth if you want) of anAggregate
. I emit events only from ahandle
method (when handling acommand
).