Architecture
Why Booking Retries and Double-Booking Need Separate Rules
By Vivek Jalondhara · 5 min read
Published
A repeated request and two competing reservations are different problems. Model their identities separately before choosing a constraint.
A booking endpoint can handle retries correctly and still double-book a resource. It can also prevent double-booking while confusing a customer whose successful response disappeared. Both bugs can look like duplicate creation, but they involve different identities.
This educational Booking Management System analysis separates a user’s intention from a resource’s occupancy. It complements an idempotent-API implementation guide by examining what each rule can and cannot prove.
Name the two questions
The retry question is whether the application has already handled this caller’s particular operation. The occupancy question is whether this resource can accept the proposed reservation. A request key addresses the first. A slot or interval rule addresses the second. Neither identity can safely be substituted for the other.
For a synthetic example, a customer retries a room reservation after losing the response. That is one intention arriving twice. In another example, two customers independently choose the same room and time. Those are two legitimate intentions competing for one resource. A design review should describe both before discussing error codes or storage.
Use replay identity for uncertain responses
A caller should reuse an operation key when retrying the same intention, and use a different key for a new one. Scope it to the caller and operation, compare the validated request, and retain an authorised way to retrieve the earlier outcome. Similar-looking payloads alone do not establish that two requests mean the same thing.
The replay result needs to identify the reservation created by that intention. Returning a generic already-exists error after a lost response leaves the customer unsure whether the booking is theirs. Conversely, a new request key must not bypass the occupancy rule just because it represents a new intention.
Choose an occupancy model that matches the product
The worked schema assumes discrete canonical slots with one active occupant per resource. Its primary key covers tenant, resource, and slot start. For this deliberately limited model, two active claims for the same identity cannot coexist. Validate that the submitted slot belongs to the allowed schedule before inserting a claim.
Arbitrary time intervals are different. Two reservations can start at different instants and still overlap, so uniqueness on the start time is insufficient. PostgreSQL range exclusion constraints can express non-overlap when configured for the relevant resource scope. Capacity greater than one, cleanup gaps, and shared equipment require their own occupancy rules.
Make the database outcome authoritative
A calendar read is a useful suggestion, not the final reservation decision. Another request can claim the slot after the calendar response. The reservation operation must atomically enforce occupancy and create the booking, with the replay result committed consistently when that contract is offered.
A check that reads free and then inserts without an enforcing rule leaves a race. Likewise, a duplicate request rejected at the slot constraint is not automatically an idempotent replay. The server must determine whether it is the known caller operation or a different booking conflict. Keep those branches visible in the application contract.
Treat cancellation and rescheduling as transitions
For the illustrative slot table, cancellation releases occupancy and updates reservation state in the same transaction. Retain the historical booking separately. Simply deleting every row associated with a time would erase useful history and could release another reservation if identity checks were incomplete.
Rescheduling requires a defined rule for acquiring the replacement and releasing the original. If the new slot is unavailable, a failed transaction should preserve the original booking. Use a consistent locking order when multiple resources are involved and handle transaction failures explicitly. Sending reminders before the authoritative transition commits can expose a state that never became real.
Review the combinations, not just each rule alone
Run four synthetic scenarios: one key twice, two keys for one slot, one key reused with changed input, and a retry after the original reservation was later canceled. Each needs an intentional result. In the last case, replay semantics and current resource state are different facts; the response contract should make the distinction understandable.
External actions remain another boundary. A booking constraint does not guarantee exactly-once charging or notification delivery. Persist follow-up intent and reconcile ambiguous provider outcomes according to their contracts. Separating replay, occupancy, and side effects makes the promised behaviour smaller and more defensible than calling the whole endpoint duplicate-safe.
One active claim for each canonical slot
Educational occupancy table for discrete slots with capacity one. Create and release claims in the same transactions as the corresponding booking transitions. Reservation history lives elsewhere.
Illustrative example · SQL · Simplified for explanation
CREATE TABLE slot_occupancy (
tenant_id uuid NOT NULL,
resource_id uuid NOT NULL,
slot_start timestamptz NOT NULL,
booking_id uuid NOT NULL,
PRIMARY KEY (tenant_id, resource_id, slot_start)
);
-- Only after authorization and canonical-slot validation:
INSERT INTO slot_occupancy (
tenant_id, resource_id, slot_start, booking_id
) VALUES ($1, $2, $3, $4);
-- A uniqueness conflict concerns occupancy, not request replay.- Identify the caller’s intention
- Validate the requested canonical slot
- Enforce occupancy with the booking write
- Commit and return the known outcome
Conceptual flow for the example above.
Tradeoff to keep in mind
The primary key does not prevent overlap between arbitrary intervals or represent capacity above one. Request deduplication and external side effects require independent rules.