Reputation: 4303
I'm facing the issue where either I can't apply DDD, or the case can't be reformulated to fit the DDD rules.
The requirements:
Product
and Location
.Location
should be reflected in Product
which user then sees. Therefore Product
references Location
.Location
might be used in several Products
.Product
might be created and deleted at any time point.Location
might be created at any time point.Location
might be deleted only if there is no Product
references this Location
.Product
does not lead to Location
deletion.There are two more entities in the model exist with the same rules as Location
, but solution should be the same as with Location
case.
Clarification 1:
Product
can't exist without being in any location
, or without being related to any location
, it MUST have a location. You might create both aggregates in any time point, but condition to create Product
is having a Location
already exist.
My thoughts:
So the problem which I see, that such requirements creates circular dependency on Product
and Location
. Because they all have own unlimited life cycle they supposed to be separate aggregation roots, but then this violates the consistency boundary, as basically one AR is trying to control another AR. So, this invariant can't be placed in any of those two. Trying to extract this into third one on the top AR also not clear how this would work.
The question:
How can I satisfy such requirements in DDD? Removing the constraint 6 is not an option. I've already tried to negotiate this with business side.
Upvotes: 0
Views: 322
Reputation: 1155
As you have correctly stated, Product
and Location
are two distinct aggregates. They don't control each other, and there is no problem in having Product
holding a reference to a single Location
. The same goes for holding a list (or even a simple counter) of Products
inside a Location
.
Now, suppose you are concerned about the Presentation Layer (eg. a UI or even an API), for example, due to potential changes to a Location
, or deletion of a Product
. In that case, you might want to consider dispatching Domain Events and write the appropriate handlers to deal with them. You might also want to consider using CQRS, and create/refresh Query Models that will be consumed by the Presentation Layer.
Upvotes: 0
Reputation: 57194
This all looks pretty straight forward; your data model for "product" includes a link (a name, an identifier, etc) to a "location".
The only tricky bit is:
Location might be deleted only if there is no Product references this Location.
You should probably review Udi Dahan: Don't Delete - Just Don't.
Important question: what's the cost to the business of a failure here?
If having products with deleted locations is expensive, then you have to design your data model with that constraint in mind. That probably means you are going to have to lock your product data when deleting locations, and maybe also locking locations when updating products.
At a guess, you're probably going to be looking at storing all products and locations in the same database, and setting up foreign key constraints to ensure that the rules are followed. Yes, there might be a performance penalty because of this sort of design, but that's justified by the fact that it solves an expensive problem.
On the other hand, if the business is a bit more forgiving, then there may be other ways of thinking about the domain model. One possibility is that Location has "state" - being either active or deprecated, where product processing is not permitted to assign products to deprecated locations (deprecated locations would be hidden from the dropdown), but the deprecated locations still appear in reports.
(In effect, we're talking about a form of soft delete here, but at the level of our actual business processes, rather than down in the plumbing.)
It might help to expand your understanding of "Location" in the business - is Location a concept where this domain model is the Source Of Truth? or are instead Locations a form of reference data? Do locations arise out of some data import / data entry process, or are they something produced by your domain model.
In the Cargo Shipping example from the original DDD book, locations are really reference data; the actual list of international ports and terminals is managed by UN/ECE, with updates being issued twice a year.
Therefore, in modeling a shipping domain, we'd be expecting location codes to appear via a data import process. Note that "delete" here has a lot of latency built into it - the national authority decides a location should be delisted, and that needs to be processed by UN/ECE, and that means that the next issue that goes out reports that the code is marked for deletion; and various processes in place to ensure that the code in question is not re-used for some extended period of time after being "deleted".
Any sort of "immediate consistency" requirement here would be absurd, given that it takes months for the information to move through the system. There has to be room in the domain model for local decisions to be made without waiting on decisions made elsewhere.
On the other hand, if you are creating a location model for UN/ECE, which is itself the Source of Truth for code assignments, then stricter consistency requirements might be appropriate.
Horses for courses.
Upvotes: 1
Reputation: 20551
Aggregates are consistency boundaries. So if you absolutely need changes to any location to be immediately reflected in products, and vice versa, then you basically need to have all locations and all products in a single aggregate and then have some form of concurrency control which effectively limits how that mega-aggregate changes (and will dramatically limit the rate at which this mega-aggregate can change).
Note that you can use infrastructure (e.g. relational DB transactions with foreign key requirements) to pretend that what's really one mega-aggregate is multiple smaller aggregates. Going this way doesn't change the fundamental performance (it's likely to have a much higher ceiling than the key-value optimistic concurrency control approach that a simple DDD implementation will use because it will allow concurrent updates to disparate parts of the mega-aggregate) and has the downside of instead of your domain logic living in one place in your code, it's now partially encoded in your DB schema (whether or not it's duplicated elsewhere in your code).
It's not really accurate to say that DDD (specifically aggregates) can't be applied or that a case doesn't fit the rules around aggregates, any more than one can say that the laws of physics can't be applied. It's just that the solution you get to when applying the requirements is not one you like.
Or it's possible that the requirements are mutually incompatible (e.g. a requirement to be able to perform X updates in some period of time with strong consistency). This is where refining requirements may be helpful, specifically around loosening consistency requirements. It's really common for a stronger consistency requirement to be assumed than the domain actually requires (asking questions like, "what actually happens if this happens..." is helpful).
Upvotes: 2
Reputation: 2847
In your point number 2, you say
Change in Location should be reflected in Product which user then sees. Therefore Product references Location.
I don't think this conclusion is correct. The way I see it, the Product does not need to know about locations, or at least, you haven't mentioned any rule that requires that. The fact that the user needs to see the Product location means that you need a way to query locations based on Product Ids, but that's a query issue, not an aggregate issue. The aggregate vs query distinction becomes apparent if you follow CQRS.
You can design a Location Aggregate that keeps a collection of Products. These products in the Location aggregate won't be actual Product Aggregates, but entities with a ProductId plus potentially other properties.
Let's look at your rules:
Same Location might be used in several Products.
This is solved by having a collection of Products in the Location aggregate
Product might be created and deleted at any time point.
A Product can exist without being in any location.
When a product is deleted, you'll need to publish an event so that all locations that contain that product can remove it (that would also require a way of finding Location aggregates referencing ProductIds)
Location might be created at any time point.
No problem. A Location can exist without referencing any products.
Location might be deleted only if there is no Product references this Location.
This is now easy to implement as the Delete operation in Location aggregate can check its Products collection.
Delete of a Product does not lead to Location deletion.
Not a problem, as Product doesn't even know about Locations
The issue with this design is that it's technically possible to add the same Product to multiple locations. I don't know if that is an issue or not. One possible solution is, when a Product is added to a Location, the Location aggregate publishes an event that the Location already containing that product will handle and automatically remove that product from the collection.
You could also avoid this scenario with a technical solution, like a unique key constraint in the DB in the LocationProducts
table.
Upvotes: 0