This is a well-formulated question, even if the domain model should use the language that the experts speak, and I would suggest that the domain experts do not talk about ProductConfigurations, ProductOptionsGroups and Options. Therefore, you should speak with a domain expert (typically the target user of the application) to understand the terms that he would use to complete such a task "on paper".
However, in the remainder of the answer, I will assume that the term used here is correct.
Also, please note that my answer is modeled after your domain description, but another description can lead to a deeply different model.
Limited context
You have 3 limited context for the model:
- A common core that contains a common concept that works like contracts. Both other aircraft will depend on this.
- Managing options related to creating and managing OptionsGroups and their dependencies (I would use a namespace called
OptionsManagement for this BC) - Product management related to creating and managing product configurations (I would use a namespace called
ProductsManagement for this BC)
Joint core
This step is simple, you just need some identifiers here, which will work as common identifiers :
namespace SharedKernel { public struct OptionGroupIdentity : IEquatable<OptionGroupIdentity> { private readonly string _name; public OptionGroupIdentity(string name) {
Settings Management
In OptionsManagement you only have one mutable object named OptionGroup , something like this (C # code with saving, checking arguments and all ...), exceptions (for example, DuplicatedOptionException and MissingOptionException ) and events raised when the group changed .
A partial definition of OptionGroup may be something like
public sealed partial class OptionGroup : IEnumerable<OptionIdentity> { private readonly Dictionary<OptionIdentity, HashSet<OptionIdentity>> _options; private readonly Dictionary<OptionIdentity, string> _descriptions; private readonly OptionGroupIdentity _name; public OptionGroupIdentity Name { get { return _name; } } public OptionGroup(string name) {
Product management
In the ProductsManagement namespace you will have - an Option value object (thus immutable) that can check its own dependencies, given the set of previously selected parameters - a ProductConfiguration object identified by ProductIdentity , which can decide which parameters should be activated if the parameters are already included. - A few exceptions, perseverance, etc.
What you can notice in the following (really simplified) example is getting an Option list for each OptionGroupIdentity , and initializing the ProductConfiguration is outside the domain itself. Indeed, simple SQL queries or custom application code can handle both.
namespace ProductsManagement { public sealed class Option { private readonly OptionIdentity _id; private readonly OptionIdentity[] _dependencies; public Option(OptionIdentity id, OptionIdentity[] dependencies) {
The domain model should contain only such business logic.
Indeed, you need a domain model if and only if the business logic is complex enough to cost isolation from the rest of the applied problems (for example, persistence). You know that you need a domain model when you need to pay a domain expert to understand what an entire application is.
I use events to get this isolation, but you can use any other technique.
So, to answer your question:
Where to store dependency mapping data?
Storage does not apply to DDD, but following the principle of least knowledge, I would only save them in a scheme dedicated to maintaining control of BC options. Domain and application services can simply query such tables when they need them.
Besides
Do we keep the mapping in the OptionGroup aggregate, however, if we do this, if someone updated the OptionGroup name and description while another user edited the mapping data, then there would be a concurrency exception in commit.
Do not be afraid of such problems until you meet them. They can simply be resolved with a clear exception informing the user. In fact, I'm not sure if the user adding the dependency will consider a safe successful commit when changing the dependency names.
You must speak with a client and domain expert to resolve this.
And BTW, the solution is ALWAYS to make things explicit!
Change the answer to new questions
OptionGroup has a OptionGroup dictionary, which is used to describe parameter descriptions.
Why is the parameter description property not part of the Option object?
In a limited context, OptionGroup (or Feature ) does not have an Option object. At first this may seem strange, even wrong, but the Option object in this context will not provide added value in this context. It is not enough storage of the description for definition of a class.
To my money, however, OptionIdentity should contain a description, not an integer. What for? Because the integer will not say anything to the domain expert. "OS: 102" doesn't matter to anyone, and "OS: Debian GNU / Linux" will be explicit in journals, exceptions, and brainstorming.
The same thing, why I would replace the terms of your example with more business-oriented ones (a function instead of a Group option, instead of options and requirements instead of a dependency): you are not a domain model, only if you have business rules so complex that it forced domain experts to develop a new, often mysterious, ordinary language to express them accurately and you need to understand this enough to create your application.
You mentioned the Option value object.
In this case, it has a member called _id type OptionIdentity , are value objects identifiers?
Ok, that’s a good question.
Identity is what we use to communicate something when we care about its changes.
In the context of ProductsManagement we do not need the evolution of options, all we want the model to develop is ProductConfiguration . Indeed, in this context, Option (or Solution with probably the best wording) is the value that we want to be unchanged .
That's why I said that Option is an object of value: we don’t care about the evolution of “OS: Debian GNU / Linux” in this context: we just want to make sure that its requirements are met using ProductConfiguration.
In the code for Option it takes an id constructor and a dependencies list.
I understand that Option exists only as part of OptionGroup (because the OptionIdentity type requires a _group member of the _group type). Is one Option allowed to refer to another Option , which may be inside another instance of the OptionGroup aggregate? Does this violate the DDD rule for storing links to aggregate roots only and not refer to things inside?
No. This is why I developed modeling patterns for common identifiers .
Usually I saved aggregated roots and their children as an entire object, and not separately, I do this by having the object / list / dictionary as a member in the aggregate root. For Option code, it accepts a set of dependencies (of type OptionIdentity[] ).
How would Options be rehydrated from storage? If this is an entity contained in another object, should it not be part of the common root and passed to the OptionGroup constructor?
No Option is not an entity at all! This value!
You can cache them if you have the correct cleanup policy. But they will not be provided by the repository: your application will call an application service, such as the following, to get them if necessary.