Multi-Entity Aggregate Best Practices : when to use child aggregate verses entities

Related to my question about creating the initial entity : when do you decide that the entity is so big/important/different that it should be a “child aggregate” rather than an entity? What are the pros/cons of modeling the relationship that way?

Our child entities are pretty independent, but are such that the deletion of the root aggregate would mean the deletion of all child entities. Does that work well with both child aggregates and aggregate entities?

I note that the advice for child aggregates is the opposite from that for entities: the Java objects for entities are created and added to the list within the EventSourcingHandler, whereas child entities use the Command Handler instead.
from: Aggregate Creation from another Aggregate - Axon Reference Guide

Creation of a new Aggregate should be done in a command handler rather than in an event sourcing handler. The rationale behind this is that you do not want to create new child Aggregates when a parent Aggregate is sourced from its events, as this would undesirably create new child Aggregate instances

Presumably so that the child replay creates the child, while the parent replay does not.

So their lifecycles are different.

But what else is different? If we want to delete the child aggregates when the parent aggregate is deleted, would we model that in the on(MyParentAgggregateDeleted) EventSourcingHandler in the parent aggregate? And would that issue commands or events to delete the child aggregates?

Well, this is a pretty tough question to answer, @rhubarb.
Whether the entities your model consists of should be their Aggregate Roots or belong as entity members to another (larger scoped) Aggregate Root is one of the main predicaments of DDD in general.

So, the most straightforward answer I can give is “it depends.”
More specifically, it depends on the domain you’re in.
Added, you (or your team members) are the domain experts, so you are best suited to make the decision.

What I can give you are some pointers, though:

  • If you are expecting high levels of concurrent access on entities, it’s best to let them be separate Aggregates. As the Aggregate “guards the consistency boundary,” you would have concurrent operations belonging to the same instance wait for one another. If those operations are intended for entities within the Aggregate, a simple step to allow them to execute concurrently is to make them actual Aggregates. The downside of this approach is that you need to take care of the references between the logical parent and the children. The sample you give that “when the parent is removed, the children should be removed” becomes a deligation process you need to take care of when you separate the root.
  • Another point to note is what data should be strongly consistent with one another. This point refers back to the concept of the Aggregate guarding the consistency boundary. If the scope of the Aggregate is the parent and the children, any operations on the parent can be taken into account at the exact moment in time by the children. And vice versa. If this is necessary for your business processes, you’re faced with a simple choice. If you can loosen this consistency somewhat in favor of concurrency, then you’re able to let the parent and children be separate aggregate roots.

That’s my two cents to the predicament.
I hope this sheds some light for you, @rhubarb.

Thanks Steven
That answer helps a lot because it clarifies what “it depends” on.
I’m thinking the concurrent access criterion might apply to me, but not sure how high.

One thing hat’s not mentioned in your response, but which worries me, is performance implications
Even if my domain model naturally argues for Aggregate + member entities, coding it that way will mean that entire list of entities is dropped after each command is handled, and rebuilt from scratch on every new command… Doesn’t that pose a performance penalty?

Here’s an analog for my use case, which might help the discussion:

Say I have

  • ProjectAggregate
    with a bunch of metadata like “Name”, “Description”, “Owner”

  • FileEntity
    with simple metadata like “FileUrl”, “LastModified”, “LastModifiedBy”

where:

  • One ProjectAggregate can have zero or more Files. It may have 100s
  • The FileEntities are independent from one another
  • Different users can be editing the content of different File in the same Project at the same time (but not many will be)
    • The Java object for the FileEntity is pretty small, but most of the action happens in editing that file referenced from the FileURL
    • I might also add some “Lock” properties to the FileEntity to do pessimistic locking.
  • The ProjectAggregate may have some aggregate (non-DDD usage) properties that kind of depend on the FileEntities, like “LargestFile”, “NewestFile”, etc, but most of its metadata is independent of the Files it contains.

So you see there is some concurrent editing of the Aggregate, but the actual updates to the entities are small and the effect on the Aggregate (updating the “NewestFile”) etc, is small, but it does exist.

Update - just discussed with Christian V, who reminded me that aggregate commands lock the aggregate, so if I want simultaneous requests on different child-entities, I had better use child-aggregates