A backend flight search & aggregation engine built with Go + Fiber. Fast provider fan-out, resilient integration, and clean layered architecture.
- 🎯 Overview
- ✨ Key Features
- 🏗️ Design Structure
- 🧠 Search Algorithm Concept
- 🔌 API Contract
- 🧪 Test Case Document (Excel-ready)
- 🚀 Run Tutorial
- 🗂️ Mock Data
This service aggregates flight data from multiple providers (Garuda, Lion, Batik, AirAsia), normalizes different schemas into one common response, and returns optimized search results with filtering, sorting, and best-value ranking.
- ✅ Multi-provider aggregation (4 mock providers)
- ✅ Unified normalized response format
- ✅ Search by route/date, including round-trip and multi-city
- ✅ Rich filtering:
- price range
- max stops
- airline
- departure/arrival time windows
- max duration
- ✅ Sorting options:
- best value (default)
- price, duration, departure time, arrival time
- ✅ Resilience and performance:
- parallel provider calls
- timeout handling
- retry with exponential backoff
- circuit breaker
- provider rate limiting
- dual cache (Redis + Memcache)
- ✅ IDR formatted currency output
The code is organized to keep business logic and infrastructure concerns clearly separated.
flowchart TB
A[app/main.go] --> B[infrastructures/app]
B --> C[server/routes.go]
C --> D[controllers]
D --> E[usecases]
E --> F[repositories]
F --> G[providers/*]
E --> H[domain models]
G --> H
F --> I[(Redis Cache)]
F --> J[(Memcache Cache)]
F --> K[pkg/circuit_breaker]
G --> L[pkg/rate_limiter]
app/main.go- app entrypoint, load config, start server
infrastructures/config- environment loading and runtime config state
infrastructures/app- Fiber bootstrap, middleware setup, health checks, dependency wiring
server/routes.go- route registration
modules/v1/flights/domain- input/output models and normalized entities
modules/v1/flights/interfaces/controllers- HTTP parsing/validation and API response envelope
modules/v1/flights/usecases- business rules: filtering, sorting, best-value scoring, dedupe, mapping
modules/v1/flights/interfaces/repositories- provider orchestration, retry, timeout, cache, circuit breaker
modules/v1/flights/interfaces/repositories/providers/*- provider-specific adapters/normalizers
pkg/circuit_breaker- call wrapper + provider breaker registry
pkg/rate_limiter- provider-level throttling utility
- Request enters
POST /api/v1/flights/search. - Controller validates and sanitizes input.
- Usecase chooses mode: one-way, round-trip, or multi-city.
- Repository checks cache and executes provider fan-out if needed.
- Each provider call runs with timeout + retry + circuit breaker + rate limiter.
- Usecase applies business pipeline and maps unified output.
- API returns envelope:
meta+data.
sequenceDiagram
autonumber
participant U as User/Client
participant API as Fiber API
participant C as Controller
participant UC as Flight Usecase
participant R as Flight Repository
participant MC as Memcache
participant RD as Redis
participant P1 as Garuda
participant P2 as Lion
participant P3 as Batik
participant P4 as AirAsia
U->>API: POST /api/v1/flights/search
API->>C: route request
C->>UC: SearchFlights(input)
UC->>R: SearchAggregated(query)
R->>MC: get(cacheKey)
alt cache miss in memcache
R->>RD: get(cacheKey)
end
alt cache hit
R-->>UC: cached normalized flights + metadata
else cache miss
par provider fan-out (parallel)
R->>P1: search(ctx)
and
R->>P2: search(ctx)
and
R->>P3: search(ctx)
and
R->>P4: search(ctx)
end
R->>R: retry + backoff + circuit breaker
R->>MC: set(cacheKey, result)
R->>RD: set(cacheKey, result)
R-->>UC: aggregated normalized flights + metadata
end
UC->>UC: validate -> dedupe -> filter -> score -> sort -> map output
UC-->>C: FlightSearchOutput
C-->>U: JSON { meta, data }
- Build cache key from query payload hash.
- Try cache read.
- On miss, execute all providers in parallel.
- For each provider:
- run through circuit breaker wrapper
- retry failures with exponential backoff (
60ms,120ms,240ms, ...) - respect context timeout
- Merge successful results and collect provider metadata.
- Cache merged normalized payload with TTL.
Order of operations in one-way search:
- Validation
- departure/arrival timestamps must exist
- arrival must be after departure
- price and seat count must be positive
- Deduplication
- signature: airline + flight number + route + departure + arrival
- keep the cheapest offer for same signature
- Filtering
- price range, stops, duration, airline, departure/arrival windows
- Best-Value Scoring
- normalize price and duration to
[0..1] - apply stop penalty
- weighted score formula:
0.55 * price0.30 * duration0.15 * stop penalty
- convert to 0..100 (higher is better)
- normalize price and duration to
- Sorting
- by selected
sortBystrategy
- by selected
- Output Mapping
- RFC3339 timestamps
- formatted duration and IDR amount
- include
best_value_score
- Round-trip: outbound + inbound search, merged as
return_flights. - Multi-city: run per segment, aggregate under
segment_results.
- ⚡ Parallel fan-out for speed
- ⏱️ Request-scoped timeout (
FLIGHT_PROVIDER_TIMEOUT_MS) - 🔁 Retry with backoff (
FLIGHT_RETRY_COUNT) - 🧯 Circuit breaker (
FLIGHT_CB_MAX_FAILURES,FLIGHT_CB_OPEN_TIMEOUT_MS) - 🧠 Cache TTL (
FLIGHT_CACHE_DEFAULT_TTL_SECONDS) - 🚦 Provider rate limiting in base provider core
POST /api/v1/flights/search
{
"origin": "CGK",
"destination": "DPS",
"departureDate": "2025-12-15",
"returnDate": null,
"passengers": 1,
"cabinClass": "economy",
"sortBy": "best_value",
"filter": {
"minPriceIDR": 400000,
"maxPriceIDR": 1500000,
"maxStops": 1,
"airlines": ["GA", "QZ"],
"departureTimeStart": "08:00",
"departureTimeEnd": "22:00"
}
}{
"meta": {
"message": "successfully searched flights"
},
"data": {
"search_criteria": {
"origin": "CGK",
"destination": "DPS",
"departure_date": "2025-12-15",
"return_date": null,
"passengers": 1,
"cabin_class": "economy"
},
"metadata": {
"total_results": 15,
"providers_queried": 4,
"providers_succeeded": 4,
"providers_failed": 0,
"search_time_ms": 285,
"cache_hit": false
},
"flights": []
}
}- Round-trip includes
data.return_flights. - Multi-city includes
data.segment_results.
File:
docs/test-cases/flight_search_test_cases.csv
Columns:
test_caserequestresponseexpected_response
- Go
1.24+ - Redis (optional, recommended)
- Memcached (optional, recommended)
- Prepare
.env(or copy from.env.example). - Key settings:
APP_PORT,APP_URLREDIS_HOST,REDIS_PORT,REDIS_PASSWORD,REDIS_DBMEMCACHE_HOST,MEMCACHE_PORTFLIGHT_CACHE_DEFAULT_TTL_SECONDSFLIGHT_RETRY_COUNTFLIGHT_PROVIDER_TIMEOUT_MSFLIGHT_CB_MAX_FAILURESFLIGHT_CB_OPEN_TIMEOUT_MSCORS_ALLOW_ORIGINS,CORS_ALLOW_HEADERS,CORS_ALLOW_METHODS,CORS_ALLOW_CREDENTIALS
make installgo mod tidymake runAlternative with build:
make build-rungo run ./app/main.goAlternative with build:
go build -o bin/rps-service ./app/main.go
./bin/rps-serviceCommand mapping from Makefile:
make install->go mod downloadmake run->go run ./app/main.gomake build->go build -o bin/rps-service ./app/main.gomake build-run-> build binary then run./bin/rps-service
Default URL (based on .env):
http://localhost:8080
curl --location 'http://localhost:8080/api/v1/flights/search' \
--header 'Content-Type: application/json' \
--data '{
"origin":"CGK",
"destination":"DPS",
"departureDate":"2025-12-15",
"returnDate":null,
"passengers":1,
"cabinClass":"economy"
}'http://localhost:8080/swagger/
Provider mock files:
app/flight-requirements/
Reference expected sample:
app/flight-requirements/expected_result.json
Copyright © 2026 Rizqi Wijaya.