Search code examples
domain-driven-designaggregaterootinvariants

DDD - Enforce invariants with small aggregate roots


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.......


Solution

  • 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.