It is a common scenario but I can't find an answer to my problem.
I've the following contexts (simplified example) as different deployments:
- Product: Definition of what items are on sale
- Inventory: Manages available stock
- Sales: Defines item prices
The product is not what is actually sold, but variations of it. The variations that can be created of a particular product have to meet certain rules. The variations that can be created inherit some attributes of the product and have to comply with certain restrictions between them.
For example, if we were talking about cars, what is sold is not a car but a car of a certain color, with an engine type, etc. But all variations are always created from a base model and share some characteristics. The price may depend on some, not all, of the characteristics of the variation.
Therefore, I have an aggregate Product
that manages the creation of the different variants. A Product
has own
properties and a list of created ProductVariant
. But, at the same time, it seems like ProductVariant
could be an AR.
Or, at least, an Entity with global id (not local to his root).
Currently it looks like this (I do not include inventory in order to make the example shorter):
Product {
id
name
colors <-- list of allowed colors
sizes <-- list of allowed sizes
...
List<ProductVariant> variants <-- current existing variants
createNewVariant(color, size, ...) {
// check new variant meet conditions
// create and add to list
variants.add(new ProductVariant)
}
}
ProductVariant {
color
size
...
}
Available stock and price are not attributes of ProductVariant
because they have their own rules. They are, or I
think they should be, in their corresponding BCs associated with each variant (option for sale).
After a new variant is created also pricing rule will most likely be created next in Sales BC and some stok added to
Inventory. But only for each ProductVariant
, not for the Product
.
How should they be created if they refers to a child of an AR?
Option 1:
A ProductVarianteCreated
event could be created with the new ProductVariant.id
and create a PriceRule
object in
Sales BC. Or create PriceRule
as response to a CreatePriceForProductVariant
command that includes
ProductVariant.id
.
In this case the problem is that PriceRule
is referencing a child of the aggregate root Product
in another BC:
+------------------------+ +------------------------+
| Product BC | | Sales BC |
| | | |
| +--------------+ | | |
| | Product AR | | | +------------------+ |
| +--------------+ | | | PriceRule AR | |
| | | | +------------------+ |
| +------------------+ | | | id | |
| | ProductVariant | <-------- productVariantId | |
| +------------------+ | | +------------------+ |
| | | |
+------------------------+ +------------------------+
Option 2:
When a CreateNewVariant
command arrives, the handler sends a CreatePriceRule
to Sales BC before creating the new
ProductVariant
. Wait to the command succed, retrieve the new PriceRule.id
and keep a reference in the variant.
+------------------------+ +------------------------+
| Product BC | | Sales BC |
| | | |
| +--------------+ | | |
| | Product AR | | | +------------------+ |
| +--------------+ | | | PriceRule AR | |
| | | | +------------------+ |
| +------------------+ | | ^ |
| | ProductVariant | | +---- | -----------------+
| +------------------+ | |
| | priceRuleId | ------------|
| +------------------+ |
| |
+------------------------+
But it doesn't feels right that a command handler sends new commands to another context. If I do so, ProductVariant
would have a reference to PriceRule
. But Product
and ProductVariant
really don't care about prices. Also, how
would I ask to Sales BC about the price of a variant if it doesn't know to which variant the price refers to?
Option 3:
ProductVariant
is created as a new root. Product
fires and event ProductVarianteCreated
with the relevant data.
But how to meet Product
invariants about allowed variants if it only has a list of ids?
Product {
id
List variantIds <-- only references
createNewVariant(color, size) {
// how to check conditions variants are not part of aggregate?
return new ProductVariant(id, ...)
}
}
ProductVariant {
id
...
}
Option4:
Allow ProductVariant
to be an Entity with his global id and have an immutable projection of ProductVariant
(ProductVariant'
) with the same id in the other contexts with less data (only the properties that are relevant for each BC). So that
it can only be modified through Product
but, at the same time, be an independent concept in other contexts.
When a new ProductVariant
is created, then an ProductVarianteCreated
is dispatched each interested BC can create
his own projection of a ProductVariant
.
But I don't know if it is right for a child of an AR to have a global ID so that it can act as a "root" in other contexts.
+------------------------+ +------------------------+
| Product BC | | Sales BC |
| | | |
| +--------------+ | | +------------------+ |
| | Product AR | | | | ProductVariant' | |
| +--------------+ | | +------------------+ |
| | | | | id, color... | |
| +------------------+ | | +------------------+ |
| | ProductVariant | | | | |
| +------------------+ | | +------------------+ |
| | id <- global id | | | | PriceRule AR | |
| +------------------+ | | +------------------+ |
| | | |
+------------------------+ +------------------------+
After writing and reading the question again, I think I would go for option 4. But I don't know if it's the right one or if there is a better way to solve the problem.
Thanks in advance for your help!