Let's say an Entity
is composed of multiple ValueObjects
. For instance, a wanderer might leave a few footprints behind.
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.
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– Robert Harvey10/19/2018 16:17:52Commented Oct 19, 2018 at 16:17
1 Answer 1
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– Double M10/19/2018 16:31:32Commented Oct 19, 2018 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 termValueObject
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 twoValueObjects
are share the same identity if their values are equal.user3347715– user334771510/19/2018 17:08:56Commented Oct 19, 2018 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 anIssue
to change status outside aMilestone
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. AnEntity
must be the single, unambiguous, and authoritative representation of it's state.user3347715– user334771510/19/2018 17:09:25Commented Oct 19, 2018 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– Double M10/19/2018 18:06:38Commented Oct 19, 2018 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 aFootprint
is the combination of it'sx
andy
properties yourRepository
will either, upon saving, has enough information to delete the appropriateFootprints
. The process is exactly same whether the "where" clause usesid
orx AND y
.user3347715– user334771510/19/2018 18:15:21Commented Oct 19, 2018 at 18:15
Explore related questions
See similar questions with these tags.