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

Campervan Rental Booking Engine

Multi-region campervan rental platform pairing a Symfony API with master-slave MySQL and an async payment queue for idempotent, retry-safe checkout at scale.

Client
Confidential · Mobility / Rental
Role
{{TODO: e.g. Solution Architect / Senior Backend Engineer}}
Period
{{TODO: e.g. 2022–2024}}
Master-slave MySQL: writes on primary, read replicas for scaling
Async Symfony Messenger queue for idempotent payment processing
Adyen, Zendesk, Segment and Airtable integrations behind one API
PHPStan L8 + PHPCS + SonarCloud quality gates on every PR

Summary

Overview

The platform is the backend API behind a campervan rental booking engine, serving a customer booking app and an internal backoffice across EU and SG regions. It manages availability, fleet, rental contracts, invoicing and payments through a single Symfony core, with real-time inventory fed by an external allotment scheduler.

The central architectural challenge was processing payments safely at booking velocity — decoupling Adyen settlement from the synchronous booking response so a checkout can retry without ever double-charging, while keeping read-heavy availability queries off the write primary.

Context

The Problem

Rental bookings couple a time-sensitive availability decision with a payment that must be exactly-once. A naive synchronous flow ties the booking response to third-party payment latency and makes retries dangerous — a timeout on the client side can trigger a second charge.

Constraints

  • Payment settlement must be idempotent and retry-safe across client timeouts and webhook redelivery.
  • Read-heavy availability and reporting queries must not contend with booking writes.
  • Availability has to stay consistent across regions despite asynchronous replication lag.
  • Booking amendments and cancellations require an auditable, compliance-grade trail.
  • Code quality must hold at team velocity across a large, long-lived Symfony codebase.

Scope

Requirements

Functional

  • Booking core with availability sync via an external allotment scheduler
  • Fleet management: cars, car events, servicing, damage and imagery
  • Payment capture and reconciliation via Adyen with webhook notifications
  • Customer accounts, profiles and rental contract lifecycle
  • Operations: invoicing, reimbursement, cleaning and car-event workflows
  • Integrations with Zendesk, Mailchimp, Segment and Airtable dynamic pricing
  • REST API (FOS REST Bundle) with JWT auth and CORS for dual frontends

Non-functional

  • High availability via master-slave MySQL replication
  • Read scaling — analytics and availability reads served from replicas
  • Idempotent payment processing decoupled through an async queue
  • Multi-region deployment across EU and SG with per-region storage
  • Distributed error tracking and structured logging
  • CI/CD quality gates: PHPStan L8, PHPCS, SonarCloud, coverage tracking
71 controllers across booking, fleet, payment, customer and ops domains123+ Doctrine entities with 278+ tracked migration versionsMaster-slave MySQL 8.0 with async read replicasMulti-environment (dev, staging, prod, sandbox) on AWS ECS + Kubernetes

System Design

Architecture

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

Payment Queue

Symfony Messenger async_pec consumers move Adyen capture and settlement off the booking request path, enabling bounded retries with memory and time limits so a stuck job never hangs a worker.

Master-Slave MySQL

Writes target the primary while availability, reporting and analytics reads are served from async replicas, isolating read churn from booking writes.

Booking Event Sourcing

Booking to BookingEvent state transitions and Blameable audit traits give a reproducible, compliance-grade trail for amendments and cancellations.

Availability Sync

An external allotment scheduler feeds real-time inventory; replica lag is masked with cache TTLs so cross-region availability stays consistent enough for rental ops.

Integration Layer

Adyen, Zendesk, Mailchimp, Segment and Airtable dynamic pricing sit behind the API in dedicated service namespaces for independent testing and scaling.

Stack

Technology Stack

Backend

PHP 8.1Symfony 4.4FOS REST BundleJWT

Data

MySQL 8.0 (master-slave)Doctrine 2.8Rediscreof spatial

Async / Integrations

Symfony MessengerAdyenZendeskSegmentAirtableMandrill

Infra / CI

AWS ECS (Fargate)KubernetesAWS S3JenkinsSonarCloudSentry

Persistence

Data Model

  • Booking (MySQL): reservation core, dates, region, contract linkage, soft-delete versioning
  • BookingEvent (MySQL): state-machine transitions forming the booking audit trail
  • Car / CarEvent (MySQL): fleet inventory, servicing, damage and event history
  • Payment (MySQL): Adyen references, capture state, reconciliation, idempotency keys
  • Customer / User (MySQL): accounts, profiles, roles, Blameable audit fields
  • Spatial data (MySQL): creof spatial types for location-based availability queries

Reasoning

Key Decisions & Trade-offs

Master-slave MySQL replication

trade-off

Async replication adds read lag and eventual consistency, but read replicas absorb analytics and availability load without contending with the write primary.

Async payment queue via Symfony Messenger

trade-off

Extra queue-lag complexity, but decouples payment settlement from the booking response and makes retries idempotent instead of double-charging.

Strict PHPStan L8 + PHPCS + SonarCloud gates

trade-off

Slower onboarding and stricter discipline, but null-reference and regression bugs are caught before staging on a large, long-lived codebase.

Kubernetes-capable deployment alongside AWS ECS

trade-off

Per-region KUBECONFIG and added operational overhead, but multi-cloud-capable, regionally isolated deployments across EU and SG.

Operations

Scaling & Reliability

Horizontal: stateless PHP-FPM tasks on ECS Fargate auto-scale on CPU and memory, deployed per region behind the gateway.

Reads vs writes: the MySQL master handles booking writes while async replicas serve availability, reporting and analytics; cache TTLs mask replication lag for inventory reads.

Payments: Messenger persists jobs to the database and processes them on consumers with memory and time limits; combined with Adyen webhooks this yields safe, bounded retries.

Resilience: Redis for sessions and counters, per-region S3 CDN for static assets, Sentry for distributed error tracking, and PHPStan L8 / PHPCS / SonarCloud gates preventing regressions from shipping.

What made this hard

Architect's Challenges

  • Idempotent payments: Adyen webhook plus async queue enable safe retry without double-charging
  • Multi-region availability consistency across async read replicas and the allotment scheduler
  • Booking-state correctness and compliance via event-sourced BookingEvent audit trails
  • Sustaining code quality at velocity with automated PHPStan, PHPCS and SonarCloud gates

Results

Outcome

  • Queue-based payment processing scales independently of booking throughput
  • Enforced quality gates: PHPStan L8, PHPCS, SonarCloud, coverage tracking per PR
  • Blue-green ECS deployments with a fast rollback path
  • {{TODO: add real metrics — uptime, booking volume, payment throughput, MTTR}}

More in Mobility & Booking