I’m looking for some guidance implementing the following problem:
We have a set of ‘states’ which a user can enter into:
1.- Registered: A user registers themselves in the system and can start placing job bids
2.- BidPlaced: When user places a bid, system will run some process behind the scenes and offer them ‘jobs’
3.- Offered: User was offered some options waiting on response
4.- Assigned: User picked a job and is actively working on it
5.- Done: User finished job
The above could be nicely modeled as a FSM, but the problem is that there are “steps” between those major states, for example: In between ‘Verified’ and ‘Offered’ we have to check that 2.1) Jobs are available 2.2) User hasn’t changed his availability 2.3) Jobs don’t have anyone assigned yet 2.4) User is sent a notification email, only when these are complete, we can move to the new state. On top of that not all those steps are required, for example: 2.4) could fail but we would still change the user state to ‘Offered’, it is considered a ‘non-critical’ requirement to enter new state. Also, we want those steps to be asynchronous when possible, in other words 2.1) and 2.2) could be triggered in parallel to increase performance.
I’m looking for a data structure or implementation which could ease solving the problem above. I’ve seen aggregate roots used in similar problems but not sure if the are a good fit for STATE1 -> step 1 -> step 2 -> step 3 -> STATE2
scenarios.
Disclaimer: The scenario above is just an example I created to explain myself, the real problem has main states and substeps which need to be completed before moving on to the next state, some of which as no required for state transition to happen
Would appreciate pointers or advice on how to approach this problem
Pablo is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
2
I think the word you’re looking for is Transitions between states. So you would have a transition from state 1 to state 2, and this transition could be a state machine in itself.
I’ve used this before but beware, your complexity can get out of hand if you’re not careful. You need to carefully encapsulate behaviour inside the states and transitions otherwise it becomes impossible to follow the code.
Here’s a simplified example to give a you an idea.
Your main state machine has a state UnregisteredState
and RegisteredState
. But inside UnregisteredState
is a UserRegistration
object, which itself is a state machine. But our main state machine doesn’t care. It just starts the transition by calling Start()
on the UserRegistration
object and waits for events. Like ResitrationCompleted
and RegistrationError
. If completed event is received, it transitions to RegisteredState
. If error it does whatever else you need.
Now, inside the UserRegistation
object, there is a state machine that similarly handles the steps/states to register a user, firing the appropriate events at the right time.
A word of caution. You can easily get stuck while waiting for the inner state machine to do its thing if it fails, and it’s not usually obvious what the hold up is. So good logging and knowing what state is doing what is essential to debug which state machine is causing the error.
1
A slightly different to the answer by @AdamB is to refactoring your code to make the distinction between states and business status.
- Program states are for recording events that take a medium to long time to execute. This includes waiting for user input and waiting for a reply email. The key here is the waiting for something requirement.
- Transition logic consists of logic that can be executed in short order. For instance looking up a database to see if a user name already exists.
- A single business status may reflect several underlying program states. For instance the status VALIDATING_CANDIDATE may be returned when the underlying program states are CHECKING_REFEREES, POLICE_CHECK and AWAITING_HR_APPROVAL.
There may well be other ways to implement this requirement. I’ve used this pattern several times and it works well.
1