Reputation: 536
In this question https://softwareengineering.stackexchange.com/questions/396151/which-layer-do-ddd-repositories-belong-to?newreg=f257b90b65e94f9ead5df5096267ef9a, I know that we should avoid talking with the repository.
But now we have one aggregate root and we need to get another aggregate root to check if the aggregate root we have is correct.
For example:
every resource has its scheme. So the resource will hold the name of its scheme.
If the resource wants to update with the new resource data, it wants to get the scheme entity and ask scheme check if the new resource data is matched by scheme validator or not.
To get the scheme entity, we need to ask the scheme repository. But they tell me we should avoid talking with the repository. If we really need to avoid, how can we get the scheme entity by its name?
Upvotes: 1
Views: 1192
Reputation: 57367
Two ideas that you should keep in mind:
To get the scheme entity, we need to ask the scheme repository. But they tell me we should avoid talking with the repository. If we really need to avoid, how can we get the scheme entity by its name?
Basic idea: entities can be found via traversal (you start from some aggregate root, then keep asking for things until you get where you want), root entities can be reached via the repository.
In the "blue book", Evans assumed that you could reach one aggregate root from another; people tend not to do that as much (when you can no longer assume that all aggregates will be stored in the same database, you suddenly need to be careful about which aggregates can change at the same time, and this in turn suggests additional constraints on your design).
In the present day, we would notice that if we aren't intending to change the scheme, that we don't need its aggregate root at all - we just need a validator, which we can create from a copy of the information.
So we treat the scheme information as reference data (see Helland, 2005).
Great - so how do we get "reference data" to the resource aggregate?
Typically in one of two ways - the simplest by far is to just pass it as an argument. The application code (usually the same place that we are pulling the resource aggregate out of its repository) is also responsible for looking up the resource data.
resource = resourceRepository.get(id)
validator = validatorRepository.get(resource.scheme)
resource.update(validator, ....)
Alternatively, we can pass the capability to lookup the validator to the resource. Naively, that might look like:
resource = resourceRepository.get(id)
resource.update(validatorRepository::get, ....)
But that violates our "rule" about where we use the repository. So now what? Two possible answers: we decide the rule doesn't apply here, OR we use a similar pattern to get what we need: a "domain service")
resource = resourceRepository.get(id)
resource.update(domainService, ....)
Domain service is a pattern that can be used for all sorts of things; here, we are using it as a convenient mechanism to access the reference data that we need.
Superficially, it looks like a repository - the significant difference here is that this domain services doesn't have affordances to change the scheme entities; it can only read them. The information is the same, but the expression of that information as an object is different (because this object only has read methods).
It's "just" design; the machine really doesn't care how we tell it what work to do. We're merely trying to arrange the code so that it clearly communicates intent to the next programmer who comes along.
Upvotes: 3