So I have been learning CQRS for the past 6 months and I noticed that one of the challenges with implementing CQRS in your system is dealing with eventual consistency and race conditions.
Scenario: You are on a page in your UI with a form and a submit button. When you click submit a web request hits your web api layer fires off a command asynchronously and returns a 200 or 202 to the client. The command will do some work and eventually update Data A to be Data B. On your UI you get back the 200/202 response and you route to the next page and query your Data on the read side to display or do some calculation. You are expecting Data B to show up but it turns out it is still Data A. If you refresh the page and it queries the Data Field and it still shows Data A, it takes you back to your form to resubmit the information you already submitted (it is currently being processed but hasn't completed and updated the field).
I am just curious at what other people use to solve this problem the following solutions I have found include:
1. Web sockets/ signal R approach - where after your Data field has been updated fire off a signal R event with Data B as the value or simply send an event saying "B is done processing, requery".
Cons: I am new to signal R but from what I have heard it is not reliable with losing connections and no message retry, messages can be lost and the user will be stuck waiting for an event that will never make it back to the client.
2. Polling - creating some kind of state in the UI to requery until Data A turns into Data B then displaying or calculating or continuing on with your workflow.
Cons: Creating state on the UI means that it is limited to that device. If the user refreshes the page, or switches devices your polling mechanism is gone and you could still find yourself in an invalid state.
3. Arbitrary Delay- after sending the web request you try to buy time by throwing a spinner up for 30 seconds, or sending them to a congratulations page hoping that the command completes in that window of time.
Cons: You run a risk of still having a race condition depending on the speed of your server/ connection. On the other hand you could have the delay too long and hurt your user experience.
4. Versioning Data - You could implement some kind of version number to the data so that {Data= A, Version = 1}. Then after you submit if you query the Data field and the version is still 1 you know your command hasn't gone through yet and you don't redirect your user back to the form.
Cons: You would still have to know when to re-query using one of the above methods.
5. Flag in Local storage - You could do somewhat of a hack and write a flag to local storage saying hey this user has submitted the form if they refresh dont navigate back to the form keep moving on and just trust that it will be successful. You could put an expiration date on the flag so that it doesn't last forever. This would work if the user refreshed the page.
Cons: The flag would either last forever or have an expiration date in which case it is just an uglier version of Arbitrary Delay. Not to mention you lose your ability to manage if the form command failed and it still wouldn't work if they switched devices.
6. Synchronous server - You COULD not saying you should but you could have your web api wait synchrounously for the command to finish and listen for the event before returning the 200.
Cons: This blocks up threads on your server and can cause some major performance issues.
7. Combo - Currently at my company we use a few of these together. We have congratulation/verification screens to delay the user, we also have signal R with a fall back route and sometimes fallback polling in case signal R fails. We haven't gone into production though so I am not sure how our app will handle high volume/ slow connections.
8. ?
I am really interested in how other people have solved this issue, I am sure there is some kind of better UI workflow pattern that would probably avoid a lot of the pain points behind eventual consistency I would love to hear about that or any other solutions people have found for this very common problem. If you have found specific libraries that facilitate your solution I would also like to hear about them as well. Also if I have misrepresented one of the solutions above or missed some key to making them more viable please do correct me.
Thanks!