Communities

Writing
Writing
Codidact Meta
Codidact Meta
The Great Outdoors
The Great Outdoors
Photography & Video
Photography & Video
Scientific Speculation
Scientific Speculation
Cooking
Cooking
Electrical Engineering
Electrical Engineering
Judaism
Judaism
Languages & Linguistics
Languages & Linguistics
Software Development
Software Development
Mathematics
Mathematics
Christianity
Christianity
Code Golf
Code Golf
Music
Music
Physics
Physics
Linux Systems
Linux Systems
Power Users
Power Users
Tabletop RPGs
Tabletop RPGs

Dashboard
Notifications
Mark all as read
Q&A

How to reason about transaction isolation during development

+4
−0

Consider the following code:

public class OnlineShoppingService {

   @Transactional
   public void cancelOrder(String id) {
       if (shipmentRepository.findShipmentForOrder(id) != null) {
           throw new ConflictException("Shipped orders can not be cancelled!");
       }
       orderRepo.findById(id).setCancelled(true);
   }

   @Transactional
   public void ship(String orderId) {
       var order = orderRepo.findById(orderId);
       if (order.isCancelled()) {
           throw new ConflictException("Cancelled orders can not be shipped!");
       }
       shipmentRepo.add(new Shipment(order));
   }

(Order and Shipment are versioned JPA entities)

At first glance, this code seems to ensure that Shipments only exist for Orders that are not cancelled. Fresh out of university, I'd have been convinced it does ("transactions are atomic, which means indivisible. We can reason about them as if they had executed in sequence").

However, in the default isolation level of most databases, read committed, an Order can become cancelled during the execution of ship(). If that happens after the status is checked, but before we commit our transaction, a Shipment is created for a cancelled Order.

How do you prevent such bugs? How do you go about writing correct code and reasoning about its correctness? And how do you explain all that to junior software developers you are mentoring?

Do you

  • hit them with the formal definition of isolation levels, explaining about non-repeatable reads, phantom reads, and ask them to verify that every method they write correctly handles all these phenomena? (seems very time consuming and error prone?)
  • sidestep the problem by cranking up the isolation level to Serializable ? (I've never seen anyone do that?)
  • impose an architecture / coding convention that prevents such errors?
  • something else?

PS: I realize this is a rather broad topic; I'm happy to receive partial answers or links to outside references or even books.

Why does this post require moderator attention?
You might want to add some details to your flag.
Why should this post be closed?

1 comment thread

General comments (3 comments)

1 answer

+2
−0

One way to go is probably to use some kind of transaction scope to include everything (SELECT from order and INSERT into shipment) with a high enough transaction isolation (e.g. SERIALIZABLE for SQL Server). However, this might prove very expensive if you need a high transaction throughput.

In practice, this is not an issue:

  • the likelihood of this happening is very low
  • even if it happens, the shipment lifecycle is long enough to allow this to be checked later. E.g. I expect the system to have multiple states for a shipment. You have just created it here in your code, but there must be some validation later on.

While this might be strange for a developer, for most folks, living in an inconsistent state for a while is perfectly natural because most things take time to settle. Examples might include the period to get a new card, ID or any type of license while the old one might be still in use.

In your particular case having a shipment for a canceled order is not a big deal as long as this case is checked upon before actually dispatching the shipment.

Of course, there are cases when this consistency must be enforced. If the likelihood of this happening is 1 in 100K and the result would be a plane crash, it cannot be allowed to happen.

Why does this post require moderator attention?
You might want to add some details to your flag.

0 comment threads

Sign up to answer this question »