3

Take the following sequence of events:

  1. Business layer requests data x and y from data layer.
  2. Data layer returns version 1 of x and y.
  3. Business layer starts performing logic based on data x and y.
  4. Another (concurrent) operation updates data x to version 2.
  5. Business layer instructs data layer to save new data z based on logic at step 3.

The data z saved in step 5 has now been saved based on inconsistent, or "stale" data. The data x became stale during the business transaction. Say for example, data x holds a flag indicating whether the creation of z data is permitted.

In past I've seen this issue dealt with by:

  1. Tightly coupling business logic with data operations. For example, business logic in RDBMS stored procedures, or application code co-mingling logic with persistence-aware data operations. or;
  2. Ignoring the issue because the likelihood x impact = too low to be concerned with.

My question: is there a viable 3rd option? Something that ensures consistency while maintaining a healthy separation of concerns between the business and data layers.

Is this something that ORMs address? I know they'll deal with optimistic concurrency for data writes, but I'm not wanting to update data x, just ensure that nothing else has updated it during the course of the business transaction. Or more specifically, that the "is z creation permitted?" flag hasn't been updated.

For any platform-specific answers or comments, I'm working with C# and Postgres.

7
  • Search for locking. Just a simple one would be: Give an error on step 5 because your data is based on version 1. So you cannot overwrite a version 2. You can do that easily by keeping the version number with the data. So you send the new data + version number. If it's not the right version number block the save. In practice you see this in business software sometimes. Other one, with things like ajax etc. you see is a warning on top of your edit form: There is new data click to reload. You see it on this website on the posts list. Those 2 can be combined. – Luc Franken Jul 15 '16 at 7:32
  • Thanks @LucFranken, I already understand the principles of locking, both optimistic and pessimistic. I'm struggling with where the logic for it resides within a layered architecture. If it's in the data layer then that leads to business logic in the data layer. If it's in the business logic layer then that leads to a leaky persistence abstraction. I'm looking for a way to do it while keeping a clean architecture. – Snixtor Jul 15 '16 at 7:45
  • The version number is part of the data, so that's where you store it. The logic to compare versions should be ideally be in the business layer. Now you have one issue. The logic of handling the atomic operation itself does belong to the data store, that's the only place you can really ensure your data. So what you can do: BL -> Start transaction, call data layer to get the current version number. Then, if ok, send update to DL. If not OK stop. End of transaction. "How you start the transaction" is for the DL. "Calling and organizing the transaction" is BL. As safeguard you can double check. – Luc Franken Jul 15 '16 at 7:49
  • That sounds like it would need a very heavy-handed transaction isolation level to work. By default, "Start transaction, get current version number, do things, save data, end transaction" would still permit a race condition because "get current version number" doesn't lock the record just because it's in a transaction. During "do things", something else could easily come along and change the version without leading to transaction rejection. – Snixtor Jul 15 '16 at 8:03
  • That's defined by the underlying way you implement that technical issue in the data layer. Transaction !== database transaction. Transaction can also be an update to the record setting a toggle locked = true. Just a first random link showing your issue from google: vladmihalcea.com/2014/09/14/… – Luc Franken Jul 15 '16 at 8:11
1

There is a truck sized hole in your use case description:

The data z saved in step 5 has now been saved based on inconsistent, or "stale" data. The data x became stale during the business transaction. Say for example, data x holds a flag indicating whether the creation of z data is permitted.

This "inconsistent" state can be reached even without concurrent updates.

  1. Business layer requests data x and y from data layer.
  2. Data layer returns version 1 of x and y.
  3. Business layer starts performing logic based on data x and y.
  4. <removed>
  5. Business layer instructs data layer to save new data z based on logic at step 3.

  1. Another (subsequent) operation updates data x to version 2.

The data z saved in step 5 has now been saved as what is now inconsistent, or "stale" data. The data x became stale after the business transaction. Say for example, data x holds a flag indicating whether the creation of z data is permitted.

The real problem is: x has implications for z. Updating x implies updating z. The mistake was updating x without updating z.

Realize that, and you can fix both the concurrent and subsequent problems. Remove z from the database. Let this value only be produced in reports that are meant to be stale. This is the same mistake as storing a persons age in the database. Don't store age. Store birthdate. Calculate age when you want to know age. Understand age is always stale when reported. That's a viable 3rd option.

If you can't do that then you have to prevent anyone from updating x without updating z at the same time. That's what transactions are for. That's only viable if you control everything that touches the database.

2
  • That "truck sized hole" is going to depend on the nature of x, y, and z. If z is "age", and x is "birth date", then z is derived data, and you shouldn't be updating it independent of x. But if x is, say, an authorisation rule like "user can delete files", you'd need a concurrent operation for a user to be able to delete files after their permission has been removed. Your answer refers to a case where z is derived from x + y, but that's not the scenario I'm interested in. – Snixtor Jun 17 '19 at 23:50
  • 1
    @Snixtor perhaps edit your question so that it doesn't sound like the problem is related to "derived data". Maybe use a different example – Christopher Francisco Jul 2 '20 at 15:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.