Main question
What are some good ways to manage state machine versions (including states themselves, state transition methods, etc) in object-oriented design, particularly during deployments when multiple versions are available?
Context/application
My team is working on re-architecting a back-end microservice with better OOP design, and we've opted to use the State pattern to model the various parts of the workflow. Execution of work in different states can often be separated by long periods of time as workflow execution is event-driven and sometimes can involve calling external services -- in other wrods: in one state, we'll call a dependency and wait until we get an asynchronous response/notification before moving to and executing work in the next state.
As such, there is a chance that we can run into a "mixed fleet" issue during software deployments when we update the state machine, the transition methods, etc. For example, suppose the "v1" state machine transitions looks like:
Start -> File Retrieved -> Hashing Complete -> File Published -> Finished
and then suppose I want to add a new state to remove personally identifiable information (PII), which would go between File Retrieved and Hashing Complete and change the state transitions out of/into those states, respectively. The "v2" state machine transitions would now look like this:
Start -> File Retrieved -> PII Removed -> Hashing Complete -> File Published -> Finished
During deployment of this change, I want to make sure that any requests that start with the OLD/"v1" state machine workflow continue to use that workflow, rather than transitioning to the NEW/"v2" state machine workflow as it rolls out to the fleet.
My current approach is to store a "version" property/field in the database records with each request, so that it's easy to figure out with which state machine version the request was initiated. Right now, I am stuck figuring out the best way to model the versions in code. Some ideas I had:
- Use separate Java packages for different versions. Use a Factory pattern or something that can instantiate state objects based on whatever version number is provided. Keeps state machine implementations fully isolated, but at the cost of an explosion of classes.
- Integrate a "version" property/field into the state classes directly. Not sure how hard this would be to manage over time, and code might get ugly.
- Something else?
Open to any suggestions. We've also considered some alternative design patterns (Pipeline, CoR, etc) so open to exploring any of those as well.
For additional context: We're using the State pattern to make the workflows idempotent, abstract the business logic from orchestration, and (most importantly) make it extremely easy for future developers to see exactly where business logic SHOULD live and how to add to/modify the workflow going forward.
Thanks!