I'm having my first attempt at DDD and I'm running into a problem with aggregate design.
My application contains 3 Entities; Graph, Node, Link. Each of these entities has a name property which can be modified by the user (which I believe makes 'name' unsuitable as an Entity id). A Graph contains a collection of Nodes and a Node has a collection of outgoing Links (for the purpose of this problem it is safe to ignore incoming links). Each Node can only be associated with one Graph at a time (but can be moved between Graphs) and similarly, each Link can only be associated with one Node at any given time (but can be moved).
The invariant(s) I am attempting to enforce is that all Entity names are unique within their parent collection. With the architecture described above, the invariant is on the actual collections, so I decided that the collection owners (Graph and Node) should both be Aggregate Roots.
The problem I have is how do I now enforce the name invariant on Node? On Link it is easy since it is hidden away inside the Node AR and as such Node can confirm that all Link renames/moves do not break this invariant. But as far as I can see, there is nothing to prevent a direct rename of Node which could break the invariant. Eventual consistency is not an acceptable option here, this must be a true system invariant.
The approach I am considering is to have Node.Rename() actually enforce the invariant, but my concern is that this involves looking inside its parent Graph to check if the rename is valid. This doesn't 'feel' right - it feels like the Graph should be the one to enforce this namespacing constraint and that Node should know nothing about it at all.
I hope this makes sense, and I look forward to hearing peoples thoughts.
Edit: The Domain Model presented above is a simplified subset of the entire Domain. Too complex for all entities to be held within a single AR.......
As you already concluded in your comment, the only aggregate root should be Graph.
There is a difference between aggregates and aggregate roots. In your example both Graph and Node are aggregates, but the object responsible for managing the entire aggregate is Graph. So this is the root.
The easiest way to get an idea if an object is an aggregate root is to ask yourself:
Does it make sense to have just this object, detached from its parent?
If the answers is no, then it's probably not an aggregate root. For example, a single Node is probably of little use when it's not part of a parent Graph. That's why you usually only have repositories for aggregate roots; to prevent yourself from having access to objects that are not part of their corresponding aggregate root.
Now on to the invariants. You stated that (emphasis mine):
all [Node] names are unique within their parent [Graph]
You basically answered your question right there. In the context of a single Node it doesn't make sense to say that its name is unique. But in the context of a Graph it does, because it is an invariant of the Graph, not the Node. So the Graph is responsible for protecting this invariant.
As for the 'god aggregate root', it's not uncommon to have a single aggregate root from a global business perspective. But an important aspect of DDD is identifying the different contexts within the system. Your system may have a high-level root containing many Graph objects. In this high-level context of managing your graphs, you're probably not even interested in the low-level Link objects in the graph.
It is important that you model your domain objects according to the context. This was one of the most important things I've come to realize in the last few months. Most people know about DDD because of repositories, or maybe because of entities and value objects, but these aren't nearly as important as bounded contexts.
Even though there is only one business concept of Something, it is perfectly fine to have multiple models that represent this concept of Something, a single implementation per context. One implementation might be an aggregate root, while the other implementation is just part of a larger aggregate, all depending on the context.
Common software mantras are about code reuse, DRY and the likes, so at first it felt wrong to have multiple classes that represent the same business concept. But once I was able to let go of this feeling and realize that each implementation had its own responsibilities, it made things so much easier :)
The solution I found to this problem came from taking a CQRS approach. I found an excellent CQRS introduction here.
In my 'write' model; Graph, Node and Link are all Aggregate Roots, but names are entirely managed by the parent collection. Thus in the write model, a Node has no idea what its own name is (meaning name updates have to go via the owning Graph). In the corresponding 'read' model, the name is associated directly with the Node (since this is useful for display).
The advantage of this solution is that it allows me to keep my ARs small, but since the 'name' info is held inside the parent collection, I have no issues with maintaining cross-aggregate invariants.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With