Booking Providers
End‑to‑end booking flows across internal tee‑time DB and external providers. The
BookingService orchestrates adapters and publishes outbox events.
Architecture
- Service:
libs/tee-time-services/src/lib/booking.service.ts - Adapter registry token:
BOOKING_ADAPTERS - Provider keys:
'tee-time' | 'mca' | ...(extensible)
Create command (core fields):
{
provider: 'tee-time' | 'mca';
tenantId: number;
slotId?: number; // required for 'tee-time'
contactName: string;
contactEmail?: string;
contactPhone?: string;
externalRef?: string; // idempotency key
raw?: unknown; // provider-specific payload (e.g., MCA)
}
Flow:
- Lookup adapter by
provider. If unsupported → error. - If adapter implements
findExisting, it runs first (read-through idempotency). - Otherwise
create(command)runs. - On success,
BookingServicepublishesbooking.confirmedvia outbox with{ provider, tenantId, slotId?, externalRef? }.
Internal provider: tee-time
- Adapter:
TeeTimeBookingAdapter - Source:
libs/tee-time-services/src/lib/tee-sheet-booking.service.ts - Behavior:
- Creates a
Bookingrow and connects the chosenTeeTimeSlot. - Idempotency:
- Hard DB check: if
externalRefpresent, reuse existingBookingwith(tenantId, externalRef)unique index. - Distributed lock: Redis NX
idemp:tt:booking:${tenantId}:${externalRef}with TTL (default 60s).
- Hard DB check: if
- Creates a
Input shape (internal):
{
provider: 'tee-time',
tenantId,
slotId,
contactName,
contactEmail?, contactPhone?,
externalRef?
}
Errors:
- Missing
slotIdortenantId→ error - Lock busy without existing booking →
Idempotency lock busy; retry
MCA provider: mca
- Adapter:
McaBookingAdapter - Source:
libs/tee-time-services/src/lib/adapters/mca-booking.adapter.ts - Writes are disabled by default; enable with
MCA_WRITES_ENABLED=1. - If
TTS_WRITE_MODE=outbox, MCA writes are forcibly disabled.
Idempotency:
- Soft lock on MCA UID: Redis NX
idemp:mca:uid:${uid}with TTL (default 60s).- If lock fail, proceeds optimistically but logs.
Strict validation (optional):
- Enable with
MCA_STRICT_RATE_VALIDATION=1or pass{ strictValidation: true }insideraw.request. - Fetches
player rate selectionsnapshot and validates per‑player:- Selected
plrateidis available for the player index - Required associations: SAGA and other questions must be answered
- Selected
Basic validation (always enforced):
uidstring presentnumberOfHoles∈ 18- At least one player
- Player 1 email/phone present
- Each player has
plname,plsurname, and a non‑zeroplrateid
Payload:
{
provider: 'mca',
tenantId,
contactName,
raw: BookingRequestDto // from @digiwedge/mca-v1-client
}
Outbox events
- Topic:
booking.confirmed(viaTeeTimeEvents.BOOKING_CONFIRMED) - Payload:
{ provider, tenantId, slotId?, externalRef? }
Environment
BOOKING_IDEMPOTENCY_TTL_SECONDS– default60MCA_WRITES_ENABLED=1– enable MCA writesMCA_STRICT_RATE_VALIDATION=1– enable strict MCA validationTTS_WRITE_MODE=outbox– force outbox‑only (disable MCA writes)
Errors and retries
- Use idempotent
externalRefon internal bookings for safe retries. - For MCA, prefer strict validation in high‑touch flows to reduce upstream errors.