From the mailing list: Concurrency Checks for Related Unchanged Documents
We got the following question on the mailing list:
…say you have reference data in one document like prices, formulas, etc.
When the user is editing or creating a document you would like to ensure that their edits are based on the current info at the time of edit.
So the document being edited or created is dependent on the other reference document and the commit should raise a concurrency error if that reference document has changed in between the time the edit started and it is committed.
Of course the reference document is not changed with the document being edited/created.
This question is interesting enough to be worth a blog post. Mostly because this was my reaction when I read the post.
Basically, the premise is wrong. The user have a document like “config/tax-rates”, and when they create an order, they need to make sure that the tax rate hasn’t changed. I am assuming here that “time between edit started & committed” is relatively low, counted in milliseconds, and that there isn’t any human involvement in this process.
The reason this is wrong is the following sequence of events (T+1 is time + 1 ms):
Scenario #1 | Scenario #2 |
|
|
According to the question above, both scenarios are absolutely correct ways for the system to behave. In practice, scenario #2 is wrong (probably).
Udi Dahan has wrote about this a lot, but I can’t find the appropriate article at this time. Update: http://www.udidahan.com/2010/08/31/race-conditions-dont-exist/
If it actually matter enough for scenario #1 to require an error, scenario #2 is also invalid. This isn’t a technical issue, it is a business issue. If you failed to update the tax rate after you were notified, you are responsible for it, right? Except that there is no way that this works. Tax changes usually take place at midnight, and are announced several days / weeks in advanced, at a minimum. And even if you were in a total news blackout, you are still responsible for the rate change, even if you never heard about it.
The way this is setup is simply wrong. You don’t try to stop orders from being processed, because you are going to miss the just completed order. Instead, you are going to do compensation logic to fix any changes that apply.
You can also tell that this is the case by the fact that this is actually pretty hard to do. Documents are independent from one another. The best way to work with such a scenario is to record the time (or the etag) when you made a decision based on a related document, then at a later point in time, you can make a decision based on whatever it changed, how old it is, etc.
That gives you proper frame of mind, not trying to count milliseconds.
Comments
Domain events
Incidentally, this is exactly the kind of excellent post I love to see from you. Informative and very pragmatic.
Sounds like an "eventual" model for consistency, of sorts.
ValidFrom / ValidTo for relatively static things like tax rates...
Ayende, Great post. I agree this is a business issue - not a technical one.
However, as you pointed out you are assuming the duration between edit and commit is in milliseconds and does not involve user interaction yet the problem outlined in the mailing list is a scenario that involves a longer time between the start of the edit and the actual commit.
What if the user is creating a report based on prices/formulas and those values change on her before she completes the report? If you don’t stop the report from being stored, at the very least it should be considered invalid. Whether that reconciliation is done just prior to saving the report or the next time you read it is a business decision.
@Miguel - I'll stipulate to your scenario. When the user "posts the report", you can do a "validation check" to make sure that the hash/etag of the reference data is still the latest. If it's not respond to the user with appropriate messaging.That's pretty straight forward code.
The only scenario remaining after that is the one outlined above in this post.
Kijana, so where's that 'wrong way'? I don't see anything wrong about your example.
Comment preview