During prototyping a simple ddd application from public transit domain I've faced a problem with one value object - Transit Pass:
Each Customer can buy a transit Pass
thatallows a passenger of the service to take either a certain number of pre-purchased trips or unlimited trips within a fixed period of time. A detailed description of these passes is represented in instances of PassDefinition
s. A Customer selects PassDefinition
that is most appropriate for him and receives a new Pass
.
In the real word, Customer Account
has Pass
es so it's pretty straightforward to put Pass
into Account
aggregate as a value object that has a pointer to PassDefinition
. This approach has 2 problems:
- What if administrator will delete
PassDefinition
? SomePass
objects may point to deletedPassDefinition
and inconsistency in context will arise. - There will be a method (hasEligiblePass) on
Account
and sincePass
has no information about validity, vehicleAllowed etc external call toPassDefinition
will be required.
Another option is to move Pass
into PassDefinition
aggregate. But:
- What if Customer will delete
Account
(while it is unlikely since the account can be activated/deactivated only by design)? - It's very common operation in the system to list all available
Pass
es for specificAccount
. PuttingPass
intoPassDefinition
will force such trivial operation to retrievePassDefinition
s and filter many results.
So the global problem is that Pass
lifecycle depends on Account
and PassDefinition
lifecycles. How to deal with such objects in DDD?
2 Answers 2
PassDefinition
looks suspiciously like a name invented by a programmer, rather than something taken from the ubiquitous language. You should probably sit down with your domain experts and hash out what's really happening in the business.
My guess - you are confusing two (or more) concepts: that of the contract established when a Customer purchases a Pass, and that of the offering in the sales catalog when the customer considering a purchase.
Taking the problem as you present it: it is fairly common to have state that references two different aggregates. That usually means another aggregate with its own life cycle that represents the relationship between the aggregates.
Of course, in such a design changes to the aggregate in the middle are isolated from the current state of each of the reference aggregates. All of the state required to validate a change to the model has to be in one place.
-
Indeed,
PassDefinition
is invented by me at the point of creating this question :) In the real system, this class has another, more domain-specific name, but I've named itPassDefinition
to make it more straightforward for non-domain related devs here.ovnia– ovnia2016年12月14日 14:59:14 +00:00Commented Dec 14, 2016 at 14:59 -
3As a general rule, with DDD you want to be cautious about abstracting/generalizing/simplifying the problem you are really facing -- the details tend to matter much more. On the other hand, your domain model code tends to be where your business advantage lives, so there's a delicate balance to be discovered on a case by case basis.VoiceOfUnreason– VoiceOfUnreason2016年12月14日 16:10:51 +00:00Commented Dec 14, 2016 at 16:10
If you put CustomerId on Pass and move it in with PassDefinition you can have
PassDomain.Pass
PassDomain.PassDefinition
PassDomain.PassRepository
PassRepository.GetValidPassFor(customerId,vehicleType,journeyDate)
Maybe split customerId into ownerId + ownerType to avoid the assumed link to Customer
Explore related questions
See similar questions with these tags.
Pass
should be an entity here. The definition details should be copied over to thePass
entity as values. Changing the definition shouldn't affect passes that were already purchased.PassDefinition def = passDefinitionRepo.findById(defId)
;bool eligible = account.hasEligiblePass(def);
. WithinhasEligiblePass
you could check if theid
of the passed definition matches the pass's definition id.PassDefinition
AR will always be immutable.