Skip to main content
edited title
Link
user161357
user161357

Child Entity or Aggregate child entity that can also be an AR in other contextRoot?

deleted 7791 characters in body
Source Link
user161357
user161357

Edit:

Ok, I'll try with the real products. The topic is about selling tickets. Each ticket is for an attraction, musuem, whatever. In my example a Product is an Attraction and ProductVariation are the ticket or entrance options to sell.

An example could be a guided visit to X museum. The museum canI have different ticket options, but there are some restrictions that must be meet. The museum may accept entrance only at some fixed hours, offer guided visit only in some languages, etc. It also specifies the age at which a person is considered an adult or a child if there is such distinction. Or, perhaps, the attraction does not allow to sell tickets for people under a certain age after some hour. These properties or rules configurations are stored in the definition of the attraction/product. Because not just any combination of session/language/age is allowed.

So I would say that the attraction itself (Product) is responsible for creating their valid ticket types (ProductVariation) and ensure that they remain valid. But oncedesign a ticket type is created, that's what will actually beeing sell. And it is the typepart of ticket/entrance that has a price and a quota. The rest of the system cares little about the rules that each attraction has to create its possible ticket types.

The price depends on other rules, discounts, groups, season that do not interest in this scope. And so the quota, because if a ticket with a guide in French sells a lot maybe they hire an extra guide and increase the quota. Or the inventory may be divided among customers...

Somehow, what needs to be modeled is the creation of tickets types that ara valid with the attraction characteristics. And also, that if someone changes the configuration of the attraction it does not introduce incompatibility with the tickets offered for sale. Therefore, I have considered to have an aggregate that knows the ticket creation rulesresponsible for X activity and keeps them always valid. Or, at least, is able to invalidate them.


Edit 2:

The thing I need to create arecreating tickets to provide a list of what is for sale. The list can be queried to show, for example, all attractions where there are tickets for a family of two adults and a child of 5. So I would say that the "main" object is the ticket. But to create it I need the configuration of the attraction. I cannot allow a user to create a ticket that accepts children if the attraction is configured only for people over 16. Likewise, I cannot allow more than 10 tickets to be created for the same attraction at the same time, because that is the way it has been established. So the attraction is also a main object, because it has the rules for ticket creation and maintenance.

One possible behavior could be that the classification of an attraction is changed from all-ages to 16+ only. Consequently, all existing tickets for the attraction that accept children under 18 would have to be removed from sale.

Therefore, on the one hand, I think that the attraction is a root that is in charge of creating or removing its tickets. But once the ticket is created, the attraction doesn't matter muchthen sell them. All the other departments only care about is the tickets thatBasically there are for sale. At most, the attraction is used to filter in some listings. But but everything else depends on the information you have on the ticket itself.


Original example:

It is a common scenario but I can't find an answer to my problem.

I've the following contexts (simplified example) as three different deploymentsparts:

  • 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.

  1. Product: to create the tickets for sale for each attraction.
  2. Prices: to assign prices according to some criterias.
  3. Qutoas: to manage the available quotas.

Therefore, I have an aggregate Product that manages the creation of the different variants. A Product has own properties and a listto take care of created ProductVariantpoint 1. But, atOnce the same timeticket is created, it seems like ProductVariant couldother people will be an AR. Or, at least, an Entity with global id (not local to his root)in charge of maintaining its selling prices and quotas.

Currently it looks like this (I do not include inventoryI am interested in order to make the example shorterpart of creating and offering a ticket option. The user will be able to see what options are available for a 5 year old child and an adult (or other combination):. The system will display a list of all the ticks that meet the search criteria.

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
  ...
}

Once an option is selected, another process will check if there is a quota and what price it has.

Available stock and price are not attributes of ProductVariant because they have their own rulesMy problem is centered on step 1. They are, or I think they should be, in their corresponding BCs associated with each variant (option for sale)Create the tickets.

After a new variantFrom the point of view of creation, I think that there is created also pricing rule will most likely be created next in Salesthe object BC and some stok added toAttraction. An attraction can be "Theme Park InventoryX" or a theater show or a museum. But only for eachThe attraction has a configuration that determines which ProductVariantTicketOptions can be created. For example, not for the Productentry times, person categories by age, languages in case of a guided tour.

How should theySo an attraction might have a "General Admission Ticket" that is valid for both children and adults. But there may be created if they refers to a child of an AR?"Park + Night Show" ticket that will be created for adults only.

Option 1:
AThe ProductVarianteCreatedAttraction event couldis who has the configuration of how a ticket can be created with. Therefore, the new ProductVariant.id and create a PriceRule object in Sales BCattraction would be the root. Or create PriceRule as response toAnd it would have a list of CreatePriceForProductVariant command that includes ProductVariant.idTicketOption.

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') withApart from the same idcreation it is also in the other contexts with less data (onlycharge of invalidating the properties that are relevant for each BC)tickets options created. So that it can only be modified through Product but, atIf the same time, be an independent concept in other contexts.

When a new ProductVariantattraction is createdclassified as "adults only" in the future, then an ProductVarianteCreated is dispatched each interested BC can create his own projection of a ProductVariantit'll have to know which ticket exists in order to invalidate those for minors.

But I don't know if itThe problem is right for a child of an AR to have a global ID so that it can actthe application as a "root" in other contextswhole will actually work with TicketOptions. It doesn't work with Attraction. Everything else is ticket-centric.

+------------------------+   +------------------------+
|  Product BC            |   |  Sales BC              |
|                        |   |                        |
| +--------------+       |   | +------------------+   |
| |  Product AR  |       |   | | ProductVariant'  |   |
| +--------------+       |   | +------------------+   |
|        |               |   | | id, color...     |   |
| +------------------+   |   | +------------------+   |
| |  ProductVariant  |   |   |        |               |
| +------------------+   |   | +------------------+   |
| | id <- global id  |   |   | |  PriceRule AR    |   |
| +------------------+   |   | +------------------+   |
|                        |   |                        |
+------------------------+   +------------------------+

My problem is that TicketOption, in the context of creation I see it as a child of an aggregate. But, at the same time, in all other contexts it would make sense for it to be a root.

After writingBut if TicketOption has an ID and reading the question again, I thinkother systems reference it. Then I would go for option 4have an external reference to a child of an aggregate. But I don't know if it's theWhich doesn't sound right one or if there is a better way to solve the problemme.

Thanks in advance for your help!Is there any way to solve this problem?

Edit:

Ok, I'll try with the real products. The topic is about selling tickets. Each ticket is for an attraction, musuem, whatever. In my example a Product is an Attraction and ProductVariation are the ticket or entrance options to sell.

An example could be a guided visit to X museum. The museum can have different ticket options, but there are some restrictions that must be meet. The museum may accept entrance only at some fixed hours, offer guided visit only in some languages, etc. It also specifies the age at which a person is considered an adult or a child if there is such distinction. Or, perhaps, the attraction does not allow to sell tickets for people under a certain age after some hour. These properties or rules configurations are stored in the definition of the attraction/product. Because not just any combination of session/language/age is allowed.

So I would say that the attraction itself (Product) is responsible for creating their valid ticket types (ProductVariation) and ensure that they remain valid. But once a ticket type is created, that's what will actually beeing sell. And it is the type of ticket/entrance that has a price and a quota. The rest of the system cares little about the rules that each attraction has to create its possible ticket types.

The price depends on other rules, discounts, groups, season that do not interest in this scope. And so the quota, because if a ticket with a guide in French sells a lot maybe they hire an extra guide and increase the quota. Or the inventory may be divided among customers...

Somehow, what needs to be modeled is the creation of tickets types that ara valid with the attraction characteristics. And also, that if someone changes the configuration of the attraction it does not introduce incompatibility with the tickets offered for sale. Therefore, I have considered to have an aggregate that knows the ticket creation rules for X activity and keeps them always valid. Or, at least, is able to invalidate them.


Edit 2:

The thing I need to create are tickets to provide a list of what is for sale. The list can be queried to show, for example, all attractions where there are tickets for a family of two adults and a child of 5. So I would say that the "main" object is the ticket. But to create it I need the configuration of the attraction. I cannot allow a user to create a ticket that accepts children if the attraction is configured only for people over 16. Likewise, I cannot allow more than 10 tickets to be created for the same attraction at the same time, because that is the way it has been established. So the attraction is also a main object, because it has the rules for ticket creation and maintenance.

One possible behavior could be that the classification of an attraction is changed from all-ages to 16+ only. Consequently, all existing tickets for the attraction that accept children under 18 would have to be removed from sale.

Therefore, on the one hand, I think that the attraction is a root that is in charge of creating or removing its tickets. But once the ticket is created, the attraction doesn't matter much. All the other departments only care about is the tickets that are for sale. At most, the attraction is used to filter in some listings. But but everything else depends on the information you have on the ticket itself.


Original example:

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!

I have to design a part of a system in responsible for creating tickets for attractions and then sell them. Basically there are three different parts:

  1. Product: to create the tickets for sale for each attraction.
  2. Prices: to assign prices according to some criterias.
  3. Qutoas: to manage the available quotas.

I have to take care of point 1. Once the ticket is created, other people will be in charge of maintaining its selling prices and quotas.

I am interested in the part of creating and offering a ticket option. The user will be able to see what options are available for a 5 year old child and an adult (or other combination). The system will display a list of all the ticks that meet the search criteria.

Once an option is selected, another process will check if there is a quota and what price it has.

My problem is centered on step 1. Create the tickets.

From the point of view of creation, I think that there is the object Attraction. An attraction can be "Theme Park X" or a theater show or a museum. The attraction has a configuration that determines which TicketOptions can be created. For example, entry times, person categories by age, languages in case of a guided tour.

So an attraction might have a "General Admission Ticket" that is valid for both children and adults. But there may be a "Park + Night Show" ticket that will be created for adults only.

The Attraction is who has the configuration of how a ticket can be created. Therefore, the attraction would be the root. And it would have a list of TicketOption.

Apart from the creation it is also in charge of invalidating the tickets options created. If the attraction is classified as "adults only" in the future, it'll have to know which ticket exists in order to invalidate those for minors.

The problem is that the application as a whole will actually work with TicketOptions. It doesn't work with Attraction. Everything else is ticket-centric.

My problem is that TicketOption, in the context of creation I see it as a child of an aggregate. But, at the same time, in all other contexts it would make sense for it to be a root.

But if TicketOption has an ID and the other systems reference it. Then I would have an external reference to a child of an aggregate. Which doesn't sound right to me.

Is there any way to solve this problem?

added 1362 characters in body
Source Link
user161357
user161357

Edit 2:

The thing I need to create are tickets to provide a list of what is for sale. The list can be queried to show, for example, all attractions where there are tickets for a family of two adults and a child of 5. So I would say that the "main" object is the ticket. But to create it I need the configuration of the attraction. I cannot allow a user to create a ticket that accepts children if the attraction is configured only for people over 16. Likewise, I cannot allow more than 10 tickets to be created for the same attraction at the same time, because that is the way it has been established. So the attraction is also a main object, because it has the rules for ticket creation and maintenance.

One possible behavior could be that the classification of an attraction is changed from all-ages to 16+ only. Consequently, all existing tickets for the attraction that accept children under 18 would have to be removed from sale.

Therefore, on the one hand, I think that the attraction is a root that is in charge of creating or removing its tickets. But once the ticket is created, the attraction doesn't matter much. All the other departments only care about is the tickets that are for sale. At most, the attraction is used to filter in some listings. But but everything else depends on the information you have on the ticket itself.


Edit 2:

The thing I need to create are tickets to provide a list of what is for sale. The list can be queried to show, for example, all attractions where there are tickets for a family of two adults and a child of 5. So I would say that the "main" object is the ticket. But to create it I need the configuration of the attraction. I cannot allow a user to create a ticket that accepts children if the attraction is configured only for people over 16. Likewise, I cannot allow more than 10 tickets to be created for the same attraction at the same time, because that is the way it has been established. So the attraction is also a main object, because it has the rules for ticket creation and maintenance.

One possible behavior could be that the classification of an attraction is changed from all-ages to 16+ only. Consequently, all existing tickets for the attraction that accept children under 18 would have to be removed from sale.

Therefore, on the one hand, I think that the attraction is a root that is in charge of creating or removing its tickets. But once the ticket is created, the attraction doesn't matter much. All the other departments only care about is the tickets that are for sale. At most, the attraction is used to filter in some listings. But but everything else depends on the information you have on the ticket itself.


Add real problem context
Source Link
user161357
user161357
Loading
let links explain some jargon
Source Link
J_H
  • 7.6k
  • 1
  • 17
  • 26
Loading
Source Link
user161357
user161357
Loading