bluetape4k-workshop · Visual Companion

Spring Boot 4 · Kotlin · Java 25

Concert Ticket Flash Sale

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.

Application StructureSpring Modulith
Final-State StoragePostgreSQL
Admission AccelerationRedis
Sale Status SALE OPEN
STANDING A · AVAILABLE INVENTORY2026 SUMMER LIVE
128
#1042
#1043
ENTER
#1045
#1046
01Waiting-Room Admission
02Inventory Hold
03Payment Confirmation
04Ticket Issuance
Redis: USER/IP Leases PostgreSQL: Inventory, Payment, and Ticket State

01 · EXAMPLE CONCEPT

Combine production failure modes in one purchase flow

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.

PROBLEM 01

High Contention When the Sale Opens

Multiple requests read the remaining inventory for the same tier and attempt holds concurrently.

Prevent: Overselling
PROBLEM 02

Repeated Clicks and Lost Responses

Resending the same purchase request must not create another payment operation or order.

Prevent: Duplicate Charges
PROBLEM 03

Unknown Payment Outcome

A payment-provider timeout is not a rejection. The final result must be queried with the same operation ID.

Prevent: Premature Inventory Release
PROBLEM 04

Cancellation and Delayed Approval

If approval arrives after cancellation, the system must block ticket issuance and track the purchase through refund completion.

Prevent: Unrefunded Orders

Why This Scenario

Ticket 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.

Inventory Invariant Across the Entire Flow

0 ≤ held + sold ≤ total

No approval, rejection, cancellation, refund, or ticket-revocation path may violate this relation.

02 · DESIGN DIRECTION

Separate fast admission control from final-state changes

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.

Redis

Temporary Coordination
  • Manage waiting-room admission and short TTLs.
  • Claim USER/IP keys atomically with one Lua script.
  • Rate-limit requests and reject concurrent purchase attempts early.
  • Block new purchase admission during an outage.

PostgreSQL

Final-State Record
  • Validate the sale window and immutable policy version.
  • Lock the inventory row and update held and sold quantities.
  • Restrict active purchases per USER/IP with unique constraints and locks.
  • Preserve payment-operation, order, refund, and ticket-processing results.

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

Deploy once while keeping business-module responsibilities separate

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.

salecontrol

Manages the sale lifecycle, sale window, and policy version.

SalePurchaseAuthority

admission

Manages the waiting room, admission grants, and USER/IP leases.

TransactionalAdmissionCommands

purchase

Enforces idempotency and changes inventory, hold, and order state.

PurchaseCommands

payment

Claims payment operations and reconciles unknown outcomes.

PaymentWorker

ticketing

Manages ticket issuance and revocation operations and deduplication records.

TicketEffectWorker

operations

Provides bounded recovery operations and operational status queries.

OperationsService
Allowed dependency direction: admission → salecontrol · purchase → salecontrol/admission · payment/ticketing → purchase · operations → each public API

04 · PROCESSING FLOW

Compare state transitions across failure scenarios

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.

Normal Purchase and Ticket Issuance

Consume the admission grant, place an inventory hold, then apply payment approval and ticket issuance in sequence.

Converged
Inventory Change
External Call
Recovery Basis

05 · IMPLEMENTATION

Connect each design concern to classes and tests

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.

Implementation structure that separates business modules within one Spring Boot application and distinguishes PostgreSQL and Redis responsibilities
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

Prepare related services and verify recovery

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.

Start PostgreSQL and Redis

Docker-Compatible Container Runtime

            
Verify:

07 · TECHNICAL PROBLEMS SOLVED

Define post-failure convergence conditions, not only a fast happy path

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.

01

Serialize Inventory Under High Contention

Lock the inventory row for each tier and apply every quantity change in the same transaction.

Result: Maintain held + sold ≤ total
02

Request Idempotency and Duplicate User Purchases

Store 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 response
03

Payment Timeout Recovery

Do not finalize a timeout as failure. Query the final outcome with the same operation ID and claim revision.

Result: Reject results from stale workers
04

Cancellation, Refund, and Ticket-Revocation Races

Restore sold inventory only when both the refund and ticket states are safe.

Result: Prevent a usable ticket and restored inventory from coexisting
05

Bound the Impact of a Redis Outage

Block only new purchase admission while continuing existing payments, refunds, and queries from PostgreSQL records.

Result: An auxiliary-store outage does not block recovery
06

Deduplicate External Effects

Use stable operation IDs and per-consumer processing records for payment, refund, and ticket operations.

Result: Apply each effect once after restart and redelivery

A purchase is complete only when inventory, payment, refund, and ticket results reach a mutually consistent state.

This example encodes those convergence conditions and recovery procedures in executable Spring Boot code and integration tests.