Architecture
The aggregator is a Go monolith plus a worker. Both speak to the same Postgres, Redis, and RabbitMQ.
Components at a glance
┌────────────────────────────┐
│ Public REST API (chi) │
│ cmd/api/main.go │
└──────────────┬─────────────┘
│
┌───────────────────────────────┼───────────────────────┐
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────────────┐ ┌─────────────┐
│ handlers/ │ │ aggregator/ │ │ webhook/ │
│ search, │ │ fan-out │ │ dispatcher │
│ orders, │ │ + breakers │ └─────┬───────┘
│ servicing │ └────────┬─────────┘ │
└──────┬───────┘ │ │
│ ┌────────┴───────────────┐ │
│ │ adapters/ │ │
│ │ ndcbase (shared base) │ │
│ │ britishairways … │ │
│ └─────────────┬──────────┘ │
│ │ │
│ ┌─────────────────────┼──────────────┐ │
│ ▼ ▼ ▼ │
│ ┌────────────┐ ┌─────────────┐ ┌─────────┐│
│ │ Airline 1 │ │ Airline 2 │ │Airline N││
│ └────────────┘ └─────────────┘ └─────────┘│
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌─────────────┐
│ cache/Redis │ │ repo/Postgres│ │ queue/Rabbit │ │ Agency URL │
└──────────────┘ └──────────────┘ └──────┬───────┘ └─────────────┘
│
┌───────┴──────┐
│ Worker │
│ cmd/worker │
└──────────────┘Process model
Two processes, each from cmd/:
apicmd/api/main.goSynchronous request handler. Owns the HTTP listener, aggregator fan-out, cache, and repos. Writes events to RabbitMQ but never reads from it.
workercmd/worker/main.goConsumes RabbitMQ events and dispatches webhooks to registered subscribers. Aggregates L2B metrics into Postgres on a schedule. Listens for airline-pushed events and republishes as order.* events.
Run multiple replicas of each behind a load balancer for HA. State lives in Postgres, Redis, and RabbitMQ — the binaries are stateless.
Layered packages
| Package | Owns |
|---|---|
| internal/handlers | HTTP routing, request decoding, response encoding. No business logic. |
| internal/handlers/middleware | Auth (API key → SHA-256 → Postgres lookup → Redis cache), rate limiting, structured request logging. |
| internal/aggregator | Fan-out across adapters, per-connector circuit breaker, global timeout. No I/O of its own. |
| internal/adapters | Abstract NDCConnector interface + concrete airline packages. |
| internal/adapters/ndcbase | Shared OAuth2 / HTTP / XML envelope plumbing. Base for all airline adapters. |
| internal/adapters/britishairways | BA-specific XML types and mapper. Embeds *ndcbase.Client. |
| internal/normalization | Version detector → transformers → enrichers → quality scorer. |
| internal/refdata | In-memory airports / airlines / aircraft tables joined into offers by enrichers. |
| internal/cache | Typed wrappers around Redis: cache-aside for offers, sliding-window rate limiter, API-key cache. |
| internal/queue | RabbitMQ publisher + consumer. |
Aggregator fan-out
When a search request arrives, the aggregator sends it to every configured adapter concurrently with a per-adapter timeout (default 8 s). Results from adapters that respond in time are merged into a single ranked list. Adapters that timeout or return errors are silently skipped — partial results are always better than a failed request.
Each adapter is wrapped in a circuit breaker: after 5 consecutive failures, the breaker opens for 30 s before a single probe request is allowed through. This prevents one misbehaving airline from dragging down every search.
Normalization pipeline
Every raw airline response passes through a pipeline before being returned to the caller:
- Version detector — infers the NDC schema version from the XML namespace.
- Transformers — version-specific rules that map airline XML fields to the canonical Offer model.
- Enrichers — join refdata (airports, aircraft types, carrier names) into each offer.
- Quality scorer — computes a quality score per offer based on data completeness.