Skip to main content
1 of 6
user avatar
user avatar

Aggregate child entity that can also be an AR in other context

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!

user161357