Let's say an Entity is composed of multiple ValueObjects. For instance, a wanderer might leave a few footprints behind.

enter image description here

Imagine I load Wanderer from a repository and delete all footsteps north of the Canadian border. Since Footstep is a value object without identity (ID), how would the repository know which ones need to be deleted in the database? Is it supposed to delete all and write back only the ones held by the domain object it was given?

Similarly, consider a Milestones holding several Issues that can either be opened or closed. I should be able to modify any Issue's state independent of whether it is grouped by a Milestone or not.

enter image description here

However, if I do close an Issue, the progress towards the Milestone (defined as the ratio of closed to total Issues contained) changes, and so does the representation of the Milestone which I'd like to update to reflect the new progress. Do Domain Events come into play here?

How are you supposed to handle such cases in the context of DDD?

  • Can you please fix the title of your question so that it asks the same question (in fewer words) that the body of your post asks? – Robert Harvey Oct 19 '18 at 16:17

For your first case, you simply need to re-phrase "delete all footsteps north of the Canadian border" to "delete all footsteps where y > latitude of border" or something slightly more sophisticated. How would an ID for each Footstep even help with the above process? You are still left with figuring out which IDs correspond to a footstep north of the border.

For your second case, an Issue does not close itself. Milestone serves as a aggregate for a group of Issue. As such each Milestone closes (and opens) it's own issues. This makes sense in terms of flow anyway right? Milestone.OpenIssue seems to be the most declarative way to create a new Issue. In terms of storage, instead of your "Issue" table having some sort of [status] field (indicating "OPEN", "CLOSED", "PENDING", etc), you would instead store [status] on a join table between "Milestone" and "Issue".

Slicing data vertically like this is an extremely powerful and important tactic when modeling a domain. It's important to identify your system in terms of behavior, not just data. In this case we have discovered, through minimal knowledge crunching, that an Issue does not have a status property!

Domain events are best-used to manage process, not synchronize state (e.g. UserCreated event is handled by your EmailService to send a welcome email). Once you begin to go down the road of trying to manage/sync state with events, your system will inevitably become quite complex and difficult to understand. The purpose of DDD is to create systems that are easy to change, and a system that cannot be understood cannot be changed.

The above isn't a rule. Of course there may be situations where you want state kept in sync (e.g. keeping denormalized read models up to date), but try your absolute best to avoid these kinds of gymnastics in your write model and always be looking for alternative strategies.

  • 1) The ID would help by being able to query for all footprints belonging to the wanderer which do not match the IDs I'm left with and deleting those. The repository has no notion of a condition on which to remove prints, all it gets is a domain object which it has to write back to the db. 2) Issues may very well exist on their own and have a status. They do not have to be grouped by a milestone. – Double M Oct 19 '18 at 16:31
  • @DoubleM 1) I don't see how an ID helps here. Either you can issue a query like DELETE FROM [Footprint] WHERE x = ? AND y = ? or you cannot delete any Footprints at all. At some point, your database has to be able to delete a row. How that row is identified is either through an ID (entity) or by the aggregate of it's values (value). The term ValueObject is not used to denote that the object has no identity, rather that it's identity is a directly result of it's value such that two ValueObjects are share the same identity if their values are equal. – king-side-slide Oct 19 '18 at 17:08
  • @DoubleM 2) There is nothing in my answer to suggest that an Issue cannot exist on it's own. That said, allowing an Issue to change status outside a Milestone presents a problem. Either you manage an issue's [status] through an anonymous (or default) Milestone for those which don't have a specific one assigned, or you rethink your system. An Entity must be the single, unambiguous, and authoritative representation of it's state. – king-side-slide Oct 19 '18 at 17:09
  • 1) The repository doesn't execute use-case relevant queries. It retrieves a wanderer and populates its collection of footprints, at a later point receives the modified wanderer object with footprints removed from the collection. See deviq.com/repository-pattern 2) I see your point, however no adequate solution to this problem. – Double M Oct 19 '18 at 18:06
  • @DoubleM 1) A Repository executes whatever queries are necessary to persist your domain. You are handcuffing yourself by adopting any other understanding. In any case, again, there is no reason an ID is necessary. If the identity of a Footprint is the combination of it's x and y properties your Repository will either, upon saving, has enough information to delete the appropriate Footprints. The process is exactly same whether the "where" clause uses id or x AND y. – king-side-slide Oct 19 '18 at 18:15

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.