High Contention When the Sale Opens
Multiple requests read the remaining inventory for the same tier and attempt holds concurrently.
Prevent: OversellingSpring Boot 4 · Kotlin · Java 25
This example handles a burst of requests for limited inventory when a sale opens. It coordinates the waiting room, inventory holds, payment confirmation, cancellation and refund, and ticket issuance through one consistent state model.
01 · EXAMPLE CONCEPT
A basic inventory-decrement example shows only the happy path. This example combines repeated requests from the same user and IP, lost payment responses, cancellation followed by delayed approval, and an auxiliary-store outage.
Multiple requests read the remaining inventory for the same tier and attempt holds concurrently.
Prevent: OversellingResending the same purchase request must not create another payment operation or order.
Prevent: Duplicate ChargesA payment-provider timeout is not a rejection. The final result must be queried with the same operation ID.
Prevent: Premature Inventory ReleaseIf approval arrives after cancellation, the system must block ticket issuance and track the purchase through refund completion.
Prevent: Unrefunded OrdersTicket sales concentrate requests on shared inventory, user, IP, and payment-operation resources within a short window. At the same time, the external payment provider may not return a definitive result immediately. Connecting concurrency control and failure recovery in one state transition makes the design decisions explicit.
0 ≤ held + sold ≤ total
No approval, rejection, cancellation, refund, or ticket-revocation path may violate this relation.
02 · DESIGN DIRECTION
Redis absorbs request bursts but does not finalize inventory reservations. A PostgreSQL transaction revalidates the sale window, duplicate USER/IP purchases, policy version, and inventory quantity before committing the state change.
Even if Redis data is lost, payment, refund, and ticket recovery that already started continues from the PostgreSQL record. A PostgreSQL outage prevents final-state changes, so the readiness check fails.
03 · IMPLEMENTATION DIRECTION
A Spring Modulith modular monolith retains the benefits of a single transaction.
Each business module exposes only its api package and does not directly
access another module's tables or internal services.
Manages the sale lifecycle, sale window, and policy version.
SalePurchaseAuthority
Manages the waiting room, admission grants, and USER/IP leases.
TransactionalAdmissionCommands
Enforces idempotency and changes inventory, hold, and order state.
PurchaseCommands
Claims payment operations and reconciles unknown outcomes.
PaymentWorker
Manages ticket issuance and revocation operations and deduplication records.
TicketEffectWorker
Provides bounded recovery operations and operational status queries.
OperationsService
04 · PROCESSING FLOW
Each scenario summarizes the results verified by the real state-transition functions and integration tests. Selecting a tab changes inventory, purchase state, payment operation, and ticket-processing result together.
Consume the admission grant, place an inventory hold, then apply payment approval and ticket issuance in sequence.
05 · IMPLEMENTATION
The diagram below is the architecture view used in the module README. The list on the right identifies the current source files that implement the core behavior. PostgreSQL and Redis integration tests verify state transitions and failure recovery.
purchase/internal/PurchaseService.kt
Validates idempotency, USER/IP protection, purchase limits, and inventory in a fixed lock order, then creates the purchase attempt in one transaction.
payment/internal/PaymentWorker.kt
Records a token and revision within a short claim window, calls the external payment provider, and applies only a valid result.
purchase/internal/RefundService.kt
Restores sold inventory only after confirming both the refund result and that the ticket was never issued or has been revoked.
redis/MultiKeyLeaseAdapter.kt
Acquires, renews, and releases USER/IP leases with one Lua script to prevent partial claims.
Ticket*IntegrationTest.kt
Verifies invariants under high contention, Redis outages, delayed approvals, duplicate delivery, and process restarts.
06 · RUN THE EXAMPLE
Manual execution requires JDK 25, PostgreSQL 18, and Redis 8. The demo profile runs only on the loopback interface, and the exposed page provides a recovery view of an existing purchase attempt rather than a creation API.
07 · TECHNICAL PROBLEMS SOLVED
The example does not assume that every request runs exactly once. It combines stable identifiers, transactions, claim tokens, and deduplication records so repeated requests and external effects converge on the same final state.
Lock the inventory row for each tier and apply every quantity change in the same transaction.
Result: Maintain held + sold ≤ totalStore the request fingerprint and key, and restrict active purchases per USER/IP with PostgreSQL unique constraints.
Result: Return the same purchase result after a lost responseDo not finalize a timeout as failure. Query the final outcome with the same operation ID and claim revision.
Result: Reject results from stale workersRestore sold inventory only when both the refund and ticket states are safe.
Result: Prevent a usable ticket and restored inventory from coexistingBlock only new purchase admission while continuing existing payments, refunds, and queries from PostgreSQL records.
Result: An auxiliary-store outage does not block recoveryUse stable operation IDs and per-consumer processing records for payment, refund, and ticket operations.
Result: Apply each effect once after restart and redeliveryThis example encodes those convergence conditions and recovery procedures in executable Spring Boot code and integration tests.