I have a Shop which sells Items for a given Price.
To be clear, this is in the context of a video game, not an enterprise ERP system. The game operator can create Shops that Players can use to buy supplies, weapons, tools, etc. Players also have the ability to create their own Shops, ideally using the same rails the game operator uses.
Additionally, this question doesn't focus on the backing persistence model. My goal is to provide just enough of an interface that game operators can decide which persistence mechanism they want to use (NoSQL, MySQL, JSON files, Text files, etc).
Let's say we had a Shop that sold three Items; Potions, Buckets, and Shields. Each one has a different Price. Additionally, we'll say that the Potion Item was incorrectly named "Potionn" (something that will be fixed later).
The User Interface would present the following to the user:
Item Name Price
Potionn $100
Bucket $10
Shield $40
The Read Model Projection to represent this Shop could look something like
{
"shop_id": "1",
"items": [
{
"item_id": "potion",
"item_name": "Potionn",// misspelled name
"price": {
"currency": "dollars",
"amount": 100
}
},
{
"item_id": "bucket",
"item_name": "Bucket",
"price": {
"currency": "dollars",
"amount": 10
}
},
{
"item_id": "shield",
"item_name": "Shield",
"price": {
"currency": "dollars",
"amount": 40
}
}
]
}
The Event Stream that fed this Projection could look something like
Item Created ("potion")
Item Created ("bucket")
Item Created ("shield")
Item Named ("potion", "Potionn")// misspelled name
Item Named ("bucket", "Bucket")
Item Named ("shield", "Shield")
Shop Created (1)
Item Added to Shop (1, "potion", (100, "dollars"))
Item Added to Shop (1, "bucket", (10, "dollars"))
Item Added to Shop (1, "shield", (40, "dollars"))
The question is in regards to what happens when an Item Name is changed, after its been added to a Shop.
Item Named ("potion", "Potion")
Here, we change the name to correct an issue (the misspelling). The Projection subsequently needs to be changed, to provide the correct name to the user. If there was only one Shop that sold Potions, we would only need to update one Projection. But, if there are hundreds or thousands of Shops that sell Potions, then all of those will need to be updated in response to the corrected Item Named Event.
Some potential solutions I've come up with are:
- Just iterate through every Shop, and update the name if it has a "potion" Item. Problems include having to know all of the Shops that exist (keeping a separate list of Shop Ids), and checking all Shops, even if they don't include a Potion Item.
- Maintain a separate index of all Shops that contain a given Item. The base projection used by the User Interface is keyed on Shop Id, and contains a list of Items in that Shop. This separate index would be keyed on Item Id, and contain a list of Shops that contain that Item. This would reduce the amount of searching needed, only going through Shops that actually had the Potion Item, instead of all Shops that exist. The problem though is having to maintain two separate models, one actually used by the user interface, and another one making reacting to certain events easier; in this case, reducing the amount of Shop Projections needing to be found, and updated.
- Maintain an internal list of Item Names, and Join this list with a slimmed down Projection of Shop Items when queried. When an Item Named Event is received, this internal list is updated, and when a Shop Items Projection is requested, the Name is injected. The problems with this include maintaining two models (shop items and item names), changing from a straight query->response model, to query->join->response model, and duplication of the Item Name list, as other systems will probably maintain their own Item Name lists if they need that information. This problem is further worsened if, in addition to Item Name, we need a Sprite/Image/Picture, Description, or anything else.
- Have a separate, out of context/domain Item Name list, and use its Domain Service to query the correct Item Name. This Projection wouldn't have to maintain the list, the list wouldn't have to be duplicated by other systems, but to query then involves querying something else too. Problems with this include querying a separate Read Model when the Shop Items Projection is queried, moving to a query->join->response model like above, and it feels like it breaks down the CQRS boundary a bit.
Core aspects that influence these solutions are:
- Is it a design smell to maintain multiple separate Read Models, even for one use-case?
- I noticed there was kind of a separation of concerns in the Read Model itself. One side is the querying of the Read Model (query->response vs query->join->response), and the other side is the updating of the Read Model (iterate through all, even if it won't be updated vs iterate only through ones we know we're going to update).
- Cross context/domain querying seems to go against the grain of CQRS. It feels awkward to try to query something from another domain/context. It feels like this projection should have absolutely all the information it needs for this use-case. If it is missing something that it needs, it seems like an indicator that there are events that it's not listening to that it should be listening to.