Architecture Overview
This page is the map of the whole system. Read it before diving into any single service; each section links to the detailed page.
Volāre is a monorepo with two products:
- The Web (byvolare.com) — the customer-facing online travel agency, an Astro site where travellers browse trips and buy them.
- Arkana (arkana.byvolare.com) — the Laravel + FilamentPHP back office where the team configures everything, and which serves the REST API the Web consumes.
System diagram
Section titled “System diagram”flowchart TB
subgraph Clients
C[Customer browser]
S[Staff browser]
AI[AI tools<br/>Claude, Hermes]
end
subgraph AWS["AWS us-east-2 (defined in infra-volare)"]
WEB[The Web<br/>Astro SSR on front servers]
subgraph Laravel["Arkana (Laravel 13)"]
API[REST API<br/>/api]
ADMIN[Filament back office<br/>/admin]
MCP[Arkana MCP<br/>/mcp/arkana]
Q[Queue worker + scheduler<br/>ops-runner]
end
DB[(PostgreSQL 18 on RDS<br/>data, cache, sessions, queue)]
S3[(S3 + CloudFront<br/>assets)]
end
EXT[External services<br/>AerTicket, Stripe, Redsys, Intermundial,<br/>Pipedrive, NetSuite, Resend…]
C --> WEB --> API
S --> ADMIN
AI --> MCP
API --> DB
ADMIN --> DB
MCP --> DB
Q --> DB
API --> EXT
Q --> EXT
ADMIN --> S3
Where each box runs in production, and how traffic is routed, is described in Infrastructure Overview.
How the apps talk
Section titled “How the apps talk”- The Web never touches the database. It renders pages server-side and calls the Arkana REST
API with a service token (
INTERNAL_API_TOKEN, a Sanctum token). Customers’ own sessions (trip page, my account) use passwordless magic links — see Client Authentication. - Prices are computed only in the backend. The Web displays the price the API returns and never recalculates it.
- Staff use Arkana at
/admin, signing in with Google (allow-listed company domains) — see Google OAuth. - AI agents use the Arkana MCP server, authenticated with OAuth (Laravel Passport) or an agent token — see Arkana MCP.
Inside Arkana
Section titled “Inside Arkana”Arkana is organised by responsibility, and business rules live in exactly one place:
| Layer | Where | Rule |
|---|---|---|
| Entry points | app/Http/Controllers (API), app/Filament (back office), app/Mcp (MCP tools), app/Console/Commands |
Thin: validate input, call a service, shape the output |
| Business logic | app/Services/<Domain> |
The single source of every rule (pricing, availability, booking lifecycle…) |
| Background work | app/Jobs, routes/console.php (schedule) |
Jobs call services; they do not re-implement logic |
| Data | app/Models, database/migrations |
Eloquent models, enums in app/Enums, authorization in app/Policies |
| AI | app/Ai |
laravel/ai agents and tools (OpenRouter by default) — see AI System |
Request flow for an API call: route → middleware → controller → Form Request validation → service → models → API Resource (JSON).
The core domain
Section titled “The core domain”flowchart LR SUP[Supplier] --> TOUR[SupplierTour] TOUR --> PT[ProductTemplate] PT --> PBM[ProductByMarket] PBM --> FC[Flight config + legs] FC --> CACHE[Dynamic flight cache] PBM --> OFF[Offer<br/>one departure date] CACHE --> OFF OFF --> BK[Booking] BK --> PAX[Passengers] BK --> PAY[Payments] BK --> FB[Flight bookings<br/>AerTicket]
- A SupplierTour owns its ProductTemplate (the trip content). Publishing it to a market creates a ProductByMarket with translations and flight configuration.
- The flight cache pre-fetches fares from AerTicket for each configured route and date.
- Offers combine a ProductByMarket, a departure date, cached flights and the supplier land rate into a sellable, priced package.
- A Booking is created from an offer at checkout and carries passengers, payments, upsells and the flight bookings issued with AerTicket.
Details: Products System, Product Entity Relationships, Offers, Bookings.
Data, cache and queues
Section titled “Data, cache and queues”Everything stateful is in PostgreSQL:
| Concern | Driver | Notes |
|---|---|---|
| Application data | PostgreSQL 18 | RDS in production; full-text search for airports |
| Cache | database |
No Redis in production |
| Sessions | database |
|
| Queues | database (jobs table) |
See below |
| Files | S3 (+ CloudFront) | Uploaded images and documents |
Redis runs in the local Sail stack but the application does not depend on it.
Queues
Section titled “Queues”Jobs are grouped into named queues so slow work cannot block urgent work. Queue order is priority:
| Queue | What runs there |
|---|---|
aerticket-tickets, aerticket-fastlane |
Ticket issuance |
aerticket-bookings, aerticket-voids |
Flight bookings and voids |
pipedrive-sync, activecampaign-sync |
CRM and newsletter sync |
offer-recalculations, offer-generation |
Offer price sweeps and generation |
flight-searches |
Filling the flight cache |
default |
Emails, notifications, everything else |
Locally, flight-searches has its own worker (queue-flights) and the rest share queue. In
production a single queue-worker container on the ops-runner consumes all of them in that
order. Scheduled tasks run in the scheduler container next to it. See
Queue System.
External services
Section titled “External services”| Service | Used for | Docs |
|---|---|---|
| AerTicket | Flight search, booking, ticketing | AerTicket |
| Stripe (Laravel Cashier) and Redsys | Card payments | Payment |
| Intermundial | Travel insurance | Insurance |
| Signaturit | E-signature of supplier contracts | Signaturit |
| Pipedrive | CRM (leads, deals) | Pipedrive |
| NetSuite | ERP / accounting | NetSuite |
| ActiveCampaign | Newsletter | Newsletter Subscriptions |
| Resend | Transactional email | Notifications |
| Google Places | Points of interest | Google Places |
| Exchange-rate providers | Supplier currency conversion | Currency Exchange |
| OpenRouter (and Anthropic, Gemini, OpenAI) | AI content tools | AI System |
| Google OAuth | Staff sign-in | Google OAuth |
| Google Tag Manager, GA4, Clarity | Web analytics | GA4 Ecommerce Tracking |
Frontend rendering
Section titled “Frontend rendering”The Web is server-rendered Astro with React islands for interactive parts (checkout, search).
frontend/astro.config.mjs selects the adapter:
- Default / production on AWS:
@astrojs/node(standalone), running in thefrontcontainer. - Cloudflare previews (
DEPLOY_TARGET=cloudflare):@astrojs/cloudflare, with sessions in KV.
See Frontend Overview.
Observability
Section titled “Observability”| Tool | Purpose |
|---|---|
| Grafana Cloud | Server and container metrics, logs, production alerts — Monitoring and Alerts |
| Laravel Nightwatch | Request, job and query performance in production |
| Sentry | Exceptions |
Spatie Health (/health, /health-dashboard) |
Application self-checks used by the load balancer — Health Monitoring |
| Telescope | Local debugging (production: ops-runner, Twingate only) |