I'm trying to figure out how to integrate one command that updates multiple Aggregates in different contexts in a video game. The components/considerations for my particular design are DDD, CQRS, Ports and Adapters, Event-Driven Architecture, Aggregate+Event Sourcing, and Entity-Component-System. It's a lot to manage, but there are a few cases that can simplify quite a number of the friction points.
This diagram kind of illustrates what I'm working with. I have 5 different models, Inventory, Item, Shop, Currency, and Wallet. This is a collaborative, disconnected environment. What I mean by that is, each of these models is designed to not necessarily know about each other, except when necessary. I want these models to be reusable, by unknown third-parties, resulting in other compositions, aside from the ones that I can think of, or the one I'll talk about here.
For instance, the Item model doesn't know about the Currency model, or the Wallet model, or the Shop model, or the Inventory model. But, the Inventory deals with storing Items, so it knows about Items. The Shop model knows about assigning prices to Items, so it knows about Items and Currencies. The Wallet model knows about storing and keeping track of money, so it knows about Currencies.
Items can be added to a specific Inventory by using an AddItemToInventory command. Money can be removed from a specific Wallet by using a RemoveMoneyFromWallet command.
When a Player wants to buy an Item from a Shop, they select the given Item, and if they have enough of the specified type of money in their Wallet, they confirm they want to buy it. At that point, the money is removed from their Wallet, and the specified Item is added to their Inventory.
Because this is in a video game, I don't want to create places where duplication can occur. There are two particular cases I want to avoid.
- An Item is added to a Player's Inventory, but money is not removed from their Wallet, allowing them to get the Item for free.
- Money is removed from a Player's Wallet, but the Item is not added to their Inventory, resulting in lost money.
I've been trying to figure out how I can avoid these particular issues. My initial thinking was that this would be managed in a single transaction. The problem though is that these are in different models, different contexts, different Aggregates. Because they are different Aggregates, they shouldn't be updated in the same transaction. Other Aggregates could be updated asynchronously, in my case via published Domain Events. But because they're updated asynchronously, that still leaves open the problems, where items are given, but money isn't taken, or money is taken, and items aren't given.
In the diagram, I have the PurchaseItem
command going into a ???
component. I don't know what it's supposed to be. I didn't want it to be given to the Shop Aggregate, because then the Shop context would have to know about Inventories and Wallets. It knows about Items and Currencies, but it feels improper for it to also know about Inventories and Wallets. The Shop should work in exactly the same way, whether the Item is added to an Inventory, and funds removed from a Wallet; or if the Item is added to a Package, and funds removed in one batch at the end of the day from a Customer Account, or any other combination of things.
So more than one Aggregate can't be updated at a time. I'm thinking that either another Aggregate needs to exist somewhere else, which becomes responsible for this interaction. This though would move the Source of Truth to this new Aggregate, making these targeted contexts, sometimes the Source of Truth, sometimes not. More like Eventual Source of Truth.
Or, maybe it's acceptable to create a new Aggregate, in a different context, which uses the same Entities and Value Objects of these other contexts. But these other contexts would still own their own data, so it would still be Eventually Consistent. I read in Implementing Domain-Driven Design about a bargain basement Domain Model, where three separate models were used, and a question was posed about whether it makes sense to introduce a new model for the interaction. My problem with that approach is that I still need these other contexts to exist as they are, because third-party developers will use them for completely different purposes. Using Items for Equipment, Money for Quest Rewards, In-game and out-of-game Shops. So I can't just condense these all into a single model, without also forcing those developers to NOT use most of the model that's available.
Additionally, all Aggregates in my design are sourced from Events. On the development side, the game developer will create a new Item by assigning a new Item Id. This gets recorded in the game files as an ItemIdAssigned event. Same thing for Currency, Shop, Wallet, Inventory, etc. But once these things are created on the developer side, they're interacted with in completely different ways by the Player. A developer can add an Item to a Shop, a Player can't. A Player can buy an Item from a Shop, a developer can't. This also indicates to me that there are different Aggregates that are at play here that I can't identify. Ones that may be sourced from all, or part, of a different Aggregate's Event Stream. Or that these Aggregates are missing functionality that I haven't been able to understand it's place.
How can I model this?