← REST APIs: integrate applications and diagnose failures
08 / 8 · 60 MIN

Concurrent changes and verifiable recovery

Use preconditions and atomic patches, interpret conflicts and define evidence needed for production.

1. The version read is part of the decision

Two operators may edit the same resource from identical reads. If each sends a complete representation without protection, the last may silently erase the first change. A strong ETag represents the observed version; If-Match conditions the operation on that version. In the experiment, both read "v1". The first changes owner and receives "v2". The second still sends "v1" and receives 412 without changing the resource. The conflict exposes intent prepared against a stale baseline. It is not a request to remove protection to finish faster.

2. Three conditions with different meanings

The lab requires If-Match and returns 428 when it is absent. With an old version, it returns 412. A weak W/"v2" tag also fails If-Match strong comparison against "v2". The wildcard means something different: it requires an existing current representation but does not compare against the previously read version. In the fixture, If-Match: * permits changing the existing resource. Do not use it instead of a specific version when preventing a lost update. These codes and conditions belong in the runbook with corresponding actions under each actual service contract.

3. Reconcile content before resubmitting

In a routing example, team A changes destination while team B intends to change owner. Team B’s old document still contains the former destination. Merely replacing the ETag with the new value would pass the precondition while still restoring old data. Read current state, compare intent and construct the authorized change that preserves concurrent work. If incompatible, obtain an owner decision. Even after reconciliation, send the new precondition to detect another race. A tight window requires a priority decision, not an assumption that changes are compatible.

4. A patch must not remain partly applied

JSON Patch allows a sequence of operations including test and replace. A test checks value and type; it neither converts string "12" into number 12 nor writes the expected value. When used with HTTP PATCH, the change must be atomic. The experiment prepares a document copy, replaces owner and then tests an incorrect state. The test fails; the copy is discarded and the complete original remains. The teaching code supports only test and replace on two string fields. It is not a complete JSON Patch library or evidence for every JSON Pointer, array or number rule. For a literal root name batch/day, the JSON Pointer path is /batch~1day: ~1 represents a slash and ~0 a tilde. /batch/day traverses two separate levels. This syntax case is studied separately; it is not executed by the limited fixture.

5. Actionable errors without exposing internals

A conflict response should help the consumer choose an action. Problem Details provides a type identifier and information about an occurrence. In the fixture, key-payload-conflict distinguishes incompatible reuse from patch-test-failed. Do not implement logic solely by comparing translated detail phrases; use the type contract and HTTP code. An occurrence reference can connect support to restricted diagnostics. Do not return tokens, stack traces or another tenant’s data as debugging help. Nor should you assume the type URL contains instructions the client must automatically open or execute.

6. From local evidence to operational acceptance

The eleven observed groups include one creation for two concurrent POST requests and rejection of a stale write. This demonstrates the executed fixture’s behavior with a process lock. A distributed implementation needs exclusive identity across workers, persistence and consistent recovery between the effect and its saved result. Test crashes before and after each boundary, restarts, response loss and expiry; compare unique operations with attempts. If external effects exist, a local transaction does not automatically make them atomic. Summarize acceptance with functional evidence, unresolved risks and a recovery owner, without promising end-to-end exactly-once.

PATCH /document
Content-Type: application/json-patch+json
If-Match: "v2"

[{"op":"replace","path":"/owner","value":"team-c"},
 {"op":"test","path":"/state","value":"approved"}]

# Fixture: state=draft -> 409; owner anterior preservado; revisão continua v2.
IN PRACTICE

Reads A and B: ETag "v1". PATCH A: 200, owner=team-a, ETag "v2". PATCH B with "v1": 412. Owner remains team-a.

Common pitfalls

Update only the ETag; remove If-Match; confuse wildcard with equality; partly apply a patch; generalize a local lock.

Related topics: HTTP and HTTPS · Concurrency control · Observability and recovery

Take this idea with you

Conflicts require reconciliation and evidence; recovery mechanisms must cover the same boundaries as effects.

Create account

Reference: RFC9110 HTTP Semantics · HTTP semantics RFC9110; OpenAPI3.2.1; selected primary standards and provider contracts consulted2026-09-30