I’m currently knee-deep in developing a turn-based game using an event-sourced architecture, and things have been running smoothly until I hit a snag with how to manage intermediate state effectively—all while trying to maintain a clean separation of concerns.
Here’s how my architecture is currently set up: I have a dispatcher that takes commands from the client and routes them to specific command handlers. These handlers generate events based on the current state without directly mutating it, which then get passed to a reducer that updates the game state. Seems pretty solid, right?
But then I encountered a situation where some commands rely on the state that will be affected by earlier events in the same command processing flow. For example, when a player drinks a “Zip Pack,” the handler needs to recognize which potion slots are empty to replace them with new random potions, but the state hasn’t been updated yet to reflect that the specific potion slot has been used. Trying to anticipate the state in the handler feels like I’m coupling it too closely to the reducer, duplicating its logic, and muddying the waters of my clean architecture.
I’ve thought of a few options to tackle this:
1. **Using a draft state within the handlers:** The handler would create a copy of the current state and apply events in a local context to determine results. This keeps things aligned with the reducer but feels like I’m circumventing my initial architecture.
2. **Predicting the reducer’s output:** I could try to anticipate what the reducer would do after the current events. However, this approach is risky because it forces me to replicate reducer logic and could lead to future inconsistencies.
3. **Creating compound events:** Instead of separate **POTION_USED** and **POTION_GAINED** events, I could combine them into something like **POTIONS_CHANGED**, which encapsulates the new state of potions. While this sounds neat, it risks obfuscating the individual actions taken and may lead to confusion for listeners that track specific events.
With all of this in mind, what’s the best direction to go in? Should I go for the draft state approach, stick with a predictive model, or consolidate events into compound forms? Would love to hear your thoughts or experiences in similar situations!
Given your current dilemma, introducing a draft (intermediate) state within your command handlers is the most pragmatic and robust approach. By locally applying events to a temporary “sandbox” version of your state inside the handler, you ensure accurate forecasting of the resulting game state without needing to replicate reducer logic externally. This maintains your single source of truth as the reducer remains authoritative, reduces coupling, and avoids the pitfalls of duplicating complex logic across handlers. Effectively, you’re creating transient states internally to better inform command processing while preserving the architectural boundaries between event generation and state mutation.
In contrast, predictive models impose redundancy, increased scope for errors, and tightly couple handlers to reducer implementation details, compromising maintainability and clarity. Compound events, while appealing at first glance, risk reducing granularity and transparency in the event stream, making debugging and listener implementations more convoluted in the long term. Therefore, leveraging a draft state for internal computation within your handlers strikes the optimal balance, enabling precise decision-making based on the implications of recent events, all while retaining the elegance and maintainability of your event-sourced architecture.
It sounds like you’re in quite a pickle with this turn-based game development! Managing that intermediate state without muddying your architecture is definitely tricky.
Let’s break down what you’ve got going on. Your dispatcher and command handlers seem to be working nicely in generating events, but it’s understandable that you’re feeling the strain when handling the intermediate state during command processing.
The options you’re considering are all valid, but each comes with its own set of trade-offs. Here’s my take:
Given all of that, it might be worth giving the draft state a shot. It keeps everything tidy and only for the current command without sticking your fingers into the actual state tree until you’re ready. Maybe just log it or comment clearly to ensure you’re not losing track of what’s going on.
Lastly, always keep in mind that what works best often depends on the specific needs of your game and your team’s future workflows. Adaptability is key! Good luck, and I hope you find that sweet spot soon!