Urgent.News

What's breaking now, across thousands of outlets.

Tech

Preventing lost updates in Symfony APIs with ETags and Doctrine

There is a concurrency problem in APIs that is easy to miss. Two clients read the same resource: Client A -> GET /documents/42 Client B -> GET /documents/42 Both receive the same version. Client A changes the title and saves it. Then Client B, still working with the older representation, sends another update. Without a concurrency check, the second request can overwrite the first one without…

Concurrency issues can arise in APIs when two clients attempt to update the same resource simultaneously. This often leads to the "lost update" problem, where the second update overwrites the first one without being aware of the change. Doctrine versioning helps mitigate this issue by using a version field to ensure database updates are safe against concurrent changes.

However, for HTTP APIs, ETags and If-Match headers provide a more specific way for clients to indicate whether a resource has changed since it was last read.

To address this problem, I created the OptimisticConcurrencyBundle, which integrates HTTP semantics with Doctrine versioned entities. The bundle allows read endpoints to be marked with the #[EntityTag] attribute, enabling the generation of strong ETags for each resource. When a client performs an update, it includes the If-Match header with the ETag it received upon reading the resource.

If the resource's version has not changed, the update proceeds normally. However, if another client has modified the resource in the meantime, the server responds with a 412 Precondition Failed status code.

The bundle employs two checks to ensure data consistency: an HTTP If-Match check and a Doctrine #[ORM\Version] check during the flush operation. This dual-check approach prevents race conditions where a stale resource representation could still be updated, even if the HTTP check initially passed. By separating these responsibilities, the bundle maintains the focus of HTTP on client representation and Doctrine on atomic database updates.

One important consideration is that ETags validate representations, not just database rows. For APIs that rely on additional factors like locale, serializer groups, related entities, user-specific fields, or query parameters, a simple entity identity + version ETag may not be sufficient. To address this limitation, the bundle supports an explicit scope and a custom EntityTagProviderInterface, allowing for more precise representation validation.

It's worth noting that this solution intentionally does not support normal Doctrine hard deletes. While an If-Match check before a controller invocation can prevent race conditions, a hard delete lacks the optimistic-lock version in its SQL WHERE condition, leaving a race window before the actual deletion. Consequently, the bundle rejects HTTP DELETE requests and relies on a versioned soft delete mechanism, which passes through Doctrine's version-checked UPDATE path.

Written by urgent.news from Dev.to's reporting — not their text. Machine-written — may contain errors; check the original before relying on it.

Read the original at dev.to →

More in Tech

Honest feedback please

hello everyone. I created this website and need your feedback on it. It's a typing speed test to help people type faster.

More from Saturday 22 August →