Domain-Driven Design – Is an Aggregate Root Responsible for Deleting Its Child Entities?

aggregatedomain-driven-designhierarchy

I am developing a large software project using DDD (Domain-Driven Design). I have an entity that acts as an aggregate root, let's call it Root. This entity refers to a collection of child entities of the type Child.

Because the use of this Child entity is an implementation detail that does not matter to clients of my Root entity, those children are solely managed by the Root entity, meaning the creation of new children and the removal of existing children all takes place in according methods of the Root entity. Pseudocode could look the following

class Root 
{
    private Collection<Child> children;

    public void addChild(ChildParameters params) 
    {
        this.children.append(new Child(params));
    }

    public void removeChildByCriteria(Criteria removalCriteria)
    {
       Child foundChild = this.children.findByCriteria(removalCriteria);
       if (foundChild) {
           this.children.remove(foundChild);
       }
    }
}

I think this is a very clean approach if you only look at the object model. However, this has to be persisted somewhere, so a Repository takes care of storing a Root entity and its children in a database.

I am struggling now with finding the right place for the logic to remove a persisted child from the repository (and therefore the database). Is it ok to let the implementation of the repository find orphaned children and remove them automatically? Or is the deletion of orphaned children domain logic that should be explicitly coded inside the root entity by passing a repository to the removeChildByCriteria method like

// ...
public void removeChildByCriteria(Criteria removalCriteria, ChildRepositoryInterface childRepository) 
{
    Child foundChild = this.children.findByCriteria(removalCriteria);
    if (foundChild) {
        this.children.remove(foundChild);
        childRepository.remove(foundChild);
    }
}
// ...

Or is it all wrong to place the logic to manage the hierarchy inside the root entity and instead a domain service or domain events and according listeners should be used?

Best Answer

You should review Udi Dahan's arguments in Don't Delete - Just Don't.

Of course, since then GDPR has become a thing. So you may want to include Forget Me as a domain concept.

Is it ok to let the implementation of the repository find orphaned children and remove them automatically?

That seems perfectly acceptable to me.

Evans, in the blue book, writes

A delete operation must remove everything within the AGGREGATE boundary at once.

I'm a bit disappointed at the lack of attention that he pays to the topic in the book.

Evans describes REPOSITORY as an abstraction of an in memory collection. Fundamentally, a delete operation breaks the link between the key (identifier) and the aggregate root, which allows the aggregate root to eventually be garbage collected in a domain agnostic way.

If your backing store were actually a RDBMS, rather than an in memory collection, then a cascade delete is a pretty reasonable approximation. That could be implemented in the data store itself, or written into your repository/ORM logic.

But if your delete protocol requires domain specific actions -- sending messages to other systems, and so on, then you should probably have a method within the aggregate that expresses that logic. Transforming a representation of an active aggregate to a representation of an end-of-lifed aggregate is a domain model responsibility.

"Delete" could reasonably mean either or both of these actions, but the responsibilities for them are clearly separated.

EDIT

I missed this on the first pass

its child entities from their repository?

Child entities don't normally have a repository of their own. Once you have decided that an entity belongs to an aggregate, it is subordinate to the aggregate root. You would normally arrange that all of the entities in the aggregate are loaded by the repository used to load the root.