</>longpham.tech
All case studies
Mobility & BookingConfidential

Multi-Venue Restaurant Reservation Platform

Multi-tenant reservation engine for independent venues, with per-venue database isolation, a custom availability engine and async guest communications on a Laravel 12 core.

Client
Confidential · Hospitality SaaS
Role
{{TODO: e.g. Solution Architect / Lead Backend Engineer}}
Period
{{TODO: e.g. 2024–2025}}
Per-venue MySQL isolation via API-key tenant-switch middleware
Custom availability and sitting-time engine as the highest-risk component
Async queued email and SMS keeps booking responses non-blocking
Controller to service to repository layering with legacy response-format parity

Summary

Overview

The system is a multi-tenant reservation platform for independent restaurants and venues. It manages the full booking lifecycle, area and table seating, per-venue availability rules, guest communications and promotional features (gift vouchers, promo codes) behind a stateless Laravel 12 API.

The core challenge was delivering strict per-tenant isolation without rebuilding the application per venue, while re-implementing a monolithic predecessor's business rules and matching its API response format exactly so existing clients keep working.

Context

The Problem

Independent venues each need their own booking rules, opening hours, seating layout, payment gateways and messaging templates, plus data isolation for compliance and simplicity. The legacy monolith coupled tenants tightly, making tenant switching slow and customization awkward.

Constraints

  • Isolate every venue's data while keeping one codebase and one deployment.
  • Re-implement availability rules that vary per venue — custom durations, sitting times, area capacity overrides and overbooking toggles — without regressions.
  • Preserve legacy API response format exactly so existing clients require no changes.
  • Keep booking responses fast while confirmations and reminders fan out to thousands of guests.

Scope

Requirements

Functional

  • Booking lifecycle: creation, confirmation, cancellation and status tracking (unconfirmed, confirmed, seated, finished)
  • Availability checking per-area and cross-area with custom durations and sitting times
  • Table management: area-based seating, capacity, assignment and fully-booked detection
  • Guest communications: confirmation and cancellation emails plus SMS reminders and venue notifications
  • Promotions: gift vouchers with redemption and balance tracking, plus promo codes
  • Reporting: booking statistics, guest export and daily handover notes
  • Per-venue settings: hours, booking rules, payment gateways and email templates

Non-functional

  • Multi-tenancy — an isolated MySQL database per venue with API-key based tenant switching
  • Async notifications — queued email and SMS so the request path never blocks on delivery
  • Legacy compatibility — logic and response-format parity with the monolithic predecessor
  • Stateless API design for horizontal scaling behind a load balancer and shared queue backend
  • Observability — Sentry error tracking with tenant-tagged structured logging
One isolated MySQL 8.0 database per venue; tenant selected per request by API keyAvailability engine centralizes booking-rule logic for every venueQueued email and SMS handled by separate worker processes with retriesStateless API instances distributed behind a load balancer

System Design

Architecture

100% · drag to pan · scroll to zoom
Rendering diagram…

Tenant-Switch Middleware

ApiPostAuth resolves the venue from the API key and rebinds the database connection per request, giving per-venue isolation from one codebase without route model binding.

Availability and Sitting-Time Engine

Centralizes the highest-risk logic — custom durations, sitting times, area capacity overrides and overbooking toggles — in one testable, auditable service.

Layered Business Core

Controller to service to repository layering keeps HTTP concerns, business rules and data access separate and supports the legacy migration path.

Notification Dispatcher

Queues confirmation and cancellation emails plus SMS reminders so guest communications run off the request path with retries.

Legacy Parity Mapping

Field mapping across service, repository and resource layers reproduces the predecessor's exact response shape, field names and error codes.

Stack

Technology Stack

Backend

PHP 8.2Laravel 12Eloquent ORM

Data

MySQL 8.0 (per-tenant)AWS S3 (barcode / PDF assets)

Frontend

ViteTailwind CSS 4.x

Notifications

Laravel Mail (SMTP)SMSGlobal HTTP APILaravel Queue

Quality / Ops

PHPUnit 11LaraTestMockeryPHPStanLaravel PintSentry

Persistence

Data Model

  • Booking: b_code (unique), area_id (0 = waitlist), date, time, party size, status (unconfirmed, confirmed, waitlist, cancelled)
  • Area: section or zone with capacity, opening hours and cutoff times
  • Table: table_id, area_id, capacity and availability state
  • Settings: per-venue config for email, SMS, payment and booking rules
  • User: minimal guest record (email); API-key driven with no login
  • OpeningHours / BookingRule: per-area hours and capacity or overbooking constraints
  • DailyNote: staff handover notes
  • GiftVoucher: promotion with redemption and balance tracking
  • BookingLabel: internal tags and categories

Reasoning

Key Decisions & Trade-offs

Separate MySQL database per venue

trade-off

More databases to operate and no cross-tenant joins, but strong isolation, simpler compliance and a bounded blast radius per venue.

API-key tenant switching in middleware (no route model binding)

trade-off

Manual model resolution in the service layer because SubstituteBindings runs before tenant selection, but stateless auth and clean per-request isolation.

Service layer owns business rules

trade-off

More files and a deeper call stack, but testable rules and a safer path for migrating logic off the monolith.

Queued email and SMS notifications

trade-off

Eventual consistency and reliance on a healthy worker, but non-blocking booking responses with retry handling.

Legacy logic and response-format parity

trade-off

Constrains design freedom and embeds some legacy shape, but existing clients need zero changes at cutover.

Operations

Scaling & Reliability

Horizontal: stateless controllers behind a load balancer; per-request tenant selection keeps instances interchangeable.

Isolation: one database per venue limits blast radius and makes backup and recovery per-venue, at the cost of cross-tenant query complexity.

Async: separate queue workers absorb email and SMS fan-out off the request path, with retries and Sentry capturing failed jobs.

Observability: Sentry plus tenant-tagged structured logging keeps failures attributable to the venue that produced them.

What made this hard

Architect's Challenges

  • Delivering per-tenant isolation from a single codebase without rebuilding the app per venue
  • Taming the availability engine — custom durations, sitting times, capacity overrides and overbooking — as the highest-risk component
  • Reproducing the monolith's exact API response format so existing clients keep working
  • Fanning out thousands of confirmations and reminders without blocking booking responses

Results

Outcome

  • Self-service venue onboarding via API keys with no manual database wiring
  • Notification failures isolated to workers and surfaced through Sentry rather than failing requests
  • {{TODO: add real metrics — venues onboarded, bookings/day, notification volume, latency}}

More in Mobility & Booking