Travel & Flight SearchField engagement

One provider's bad day should not become the product's bad day, which makes the work isolation first and integration second.

Architecture and hardening lead.
98.95%
API success, from 90.6%
1.2%
search failures, from 8.4%
1.2%
duplicate results, from 12.2%
0.9%
empty results from provider failure, from 6.8%
Search coreone normalized result shapeadapter 1provideradapter 2failingadapter 3providera failing provider gives a smaller result set, never a silently incomplete one
Each provider sits behind its own adapter, presenting one internal shape to the search core. A provider that fails is cut out at its own boundary, so the search returns the rest of the market instead of an empty page.
What was at stake

Each flight data provider arrives with its own schema, its own authentication, its own rate limits and its own way of failing. Wired in directly, those differences leak into every corner of the application. Then a single provider outage empties the results page for every user, when all it should have removed was the share of the market that provider supplied.

How this works

Several providers can sell the same seat and describe it in ways that do not match.

A seat on a flight is created once, by the airline that operates it, and then sold through many channels at the same time: the airline's own systems, the large distribution networks that travel agencies book through, and aggregators and resellers built on top of those. A search product connected to several providers is usually looking at the same underlying seats through several windows at once. Each window carries its own price, its own markup and its own idea of what the price includes.

Each window also describes the seat in its own vocabulary. One physical flight can carry several flight numbers, because partner airlines each sell it under their own code while only one of them actually flies it. Times may arrive in local time or in a universal reference time. One provider names the airport, another names the city, and a city code can cover several airports. A price may or may not include taxes, booking fees or a checked bag, and it may be in a different currency. Durations, layovers and the boundaries between segments are structured differently. None of this is wrong. It is just not the same.

So before two results can be compared, both have to be translated into one shared description: which airline operates each flight, exactly where and when it departs and lands in a common time reference, and what the price covers. Only then can a product recognise that two listings are the same journey, merge them, and keep the better offer. Providers fail in their own vocabularies too. One times out, one returns an error, one slows down under load, and one returns a tidy empty answer that looks exactly like there being no flights. The layer that does the translating is the natural place to notice each of those.

One airline operates the flight
the seat exists once
Sold through several channels
airline, distribution networks, resellers
Each describes it differently
flight codes, time zones, city or airport, what the price includes
Translate to one description
operator, exact airports, common time, total price
Recognised as one journey
merge the copies, keep the better offer
Duplicates are a feature of how flights are sold. Merging them has to be designed.
FigureHow one seat turns into several listings that do not look alike. The same flight is sold through more than one channel, each describes it differently, and they can only be recognised as one journey once every listing has been translated into a shared description.
The constraint

Connecting one provider is a short job. Living with several, each changing and failing on its own schedule, is the larger one. That takes a layer between the providers and the product: one adapter per provider, a single internal shape behind all of them, deduplication across sources that sell the same seats, and every failure held inside the adapter where it started so it never reaches the search core.

Where it usually breaks

Multi-provider search usually breaks at the seams between providers.

When providers are integrated inline, each one's quirks end up spread through the application. The ranking code grows a special case for one provider's time format. The pricing code remembers that another leaves out a fee. Changing or adding a provider means touching all of it, and every change risks breaking something that had nothing to do with that provider.

The more damaging pattern is failure that travels. A search that waits for every provider before answering is only as available as its least reliable source. If one provider is down, rate limiting or slow, the whole search fails or times out and the user sees an empty page. Most of the market was available the whole time, and none of it was shown.

01

The outage that empties the page

One provider fails and the search returns nothing, even though every other provider answered.

02

The same flight twice

Overlapping resellers return the same journey and both copies reach the user, often at different prices, which looks like an error and undermines every other price on the page.

03

The silent empty answer

A struggling provider returns a well-formed empty response. It reads as no flights available and is never treated as an outage.

04

The quirk that leaked

A provider's formatting oddity is handled inside ranking or pricing code, and breaks the next time that provider changes its format.

05

The retry storm

Every search retries a provider that is already struggling, which keeps it struggling and holds every search open longer.

Search core calls every provider
directly, with no layer in between
Most providers answer
their inventory is ready
One provider times out
an outage, a rate limit or a slow patch
Core waits for all of them
the search cannot finish without the last answer
The failure point
One provider's timeout becomes every user's empty page
the healthy providers' results were there and never shown
The outage is not what empties the page. The coupling is.
Containment means the failing provider costs its own share of results and nothing more.
FigureHow a single provider's trouble becomes everyone's empty page. Most providers answered correctly. Because the search core called them directly and waited for all of them, one timeout decided the outcome for the whole search.
The fork

The reflex, and the fix.

Road not taken

Integrate each provider where it is called

Pull

It is the shortest path to a working call, and there is no abstraction to design.

Why not

Every provider change then ripples through the application, and one provider outage returns an empty result set when it should return a smaller one.

Road taken

An adapter per provider behind one interface

Accepted

An internal schema to design and maintain, and a translation layer between it and every provider.

Bought

Provider changes stop at the adapter. Overlapping inventory is deduplicated once. A failing provider makes the result set smaller and leaves it standing.

Decision

Put the boundary at the provider, because that is where the variation lives and where failures start.

How it was built
01Search core
›
02Normalized interface
›
03Per-provider adapters
›
04Dedup and merge
›
05Degrade
01

One adapter per provider

Each adapter owns its provider's schema, authentication and rate-limit behaviour, and hands back results in the canonical internal shape. Nothing above the interface knows, or needs to know, which provider answered.

02

A canonical internal result

The search core reasons about one shape. Ranking, viability and pricing logic are each written once, against that shape, and never again per provider.

03

Deduplication across overlapping sources

Providers resell overlapping inventory, so the same itinerary arrives more than once. Merging happens after normalisation, because that is the first point at which two results from different providers can be compared at all.

04

A circuit breaker on every adapter

A provider that starts failing is cut off at its own adapter, with a fallback, so the search returns the rest of the market instead of nothing.

Inside the key decisions

What each choice cost, and what it bought.

ADrawing the boundary at the provider

Every provider gets its own adapter, and that adapter is the only code that knows the provider exists. The cost is an adapter to write and maintain for each source, and a translation step on every response. What it buys is that a provider changing its format, its authentication or its limits becomes a change in one file. Adding a provider means writing an adapter, and the search core is left alone. Inline integration spreads each provider's variation across the codebase, so the cost of every change grows with the number of providers.

BDesigning one canonical result

A single internal shape had to be designed up front, which meant deciding how to represent everything providers disagree about: codeshare flight numbers, time references, what a price includes. That shape then becomes something the team owns and maintains. In return, ranking, viability and pricing are written once. Without it, every rule in the product would be multiplied by the number of providers, and each copy would drift from the others.

CDeduplicating after normalisation

Two listings of the same journey cannot be matched while they are still in their providers' formats, because they do not even name the flight the same way. So merging happens after translation, on the shared description. The merge step has to decide when two results are the same journey and which offer survives, and that logic needs care. It is what took duplicate results from 12.2 to 1.2 percent, and it is why a traveller no longer sees the same flight twice at two prices.

DContaining failure with a circuit breaker per adapter

A circuit breaker watches a provider's responses. When failures pile up, it stops sending that provider traffic for a while and answers from a fallback, then tries again later to see whether the provider has recovered. The cost is deliberate: during that pause the product does not ask a provider that might have answered, and loses whatever it would have returned. What it buys is that one failing source costs its own share of the results and nothing else. Empty results caused by provider failure fell from 6.8 to 0.9 percent. Retrying a struggling provider from every search is the alternative, and it tends to keep the provider down and hold every search open.

Search coreranking, viability and pricing, each written once
Canonical result shapethe only shape the core ever reasons about
Deduplication and mergeonly possible after translation
Circuit breaker per adaptera failing provider is paused, not waited on
One adapter per providerowns schema, authentication and rate limits
Providerseach changes and fails on its own schedule
Variation and failure both stop at the layer where they enter.
FigureWhere each responsibility sits once the layer is in place. Provider variation and provider failure are both absorbed at the bottom, so the search core at the top only ever sees one shape of result and never sees an outage directly.
How it was measured

A single headline number hides where a system fails. This work was scored on the dimensions that actually decide whether it holds in production, measured on real, held-out cases rather than the demo path.

API success rate across providersEnd-to-end search failure rateDuplicate rate in returned resultsEmpty-result rate attributable to provider failure
Before the layer
API success
90.6%
Search failures
8.4%
Duplicate results
12.2%
Empty results from provider failure
6.8%
After the layer
API success
98.95%
Search failures
1.2%
Duplicate results
1.2%
Empty results from provider failure
0.9%
an adapter per provider
Measured before and after, on the delivered system.
FigureThe same four measures before and after the isolation layer. API success went up, and every way a provider problem used to reach a traveller went down: failed searches, duplicate listings, and empty pages caused by a provider failing.
figures

Success, failure, duplicate and empty-result figures are measured before and after, on the delivered system.

What it produces
Without this discipline

Provider calls scattered through the application, the same itinerary listed more than once, and an empty results page whenever any one provider goes down.

This system

An isolation layer that took API success from 90.6 to 98.95 percent, search failures from 8.4 to 1.2 percent, duplicate results from 12.2 to 1.2 percent, and empty results caused by provider failure from 6.8 to 0.9 percent.

one adapter per providerone internal shapededup after normalisingcontain failure at the boundary
The operating envelope

What it owns, and what it hands to a person.

Handled with confidence
Providers with a documented interface
Overlapping inventory across sources
Single-provider outage and rate limiting
Flagged for review
A provider returning well-formed but wrong data
Sustained degradation across several providers at once
Out of scope by design
Inventory no integrated provider exposes
Guaranteeing completeness during a provider outage
The honest limit

The layer contains provider failure. It cannot produce the inventory a failed provider would have returned, so a degraded search gives an honestly smaller result set and never one dressed up as complete. It also cannot tell when a provider returns well-formed data that is simply wrong. That case is flagged, and it stays outside what the layer can guarantee.

What it generalizes to

Any integration with several sources: one adapter per source, one internal shape, deduplication after normalising, and failure contained in the adapter so it never reaches the core.

What to take from this

The same seat is usually sold through several providers at once, so a multi-provider search will see duplicates by design and has to merge them deliberately.

Two listings can only be compared once both are translated into one shared description, including who operates the flight, a common time reference and what the price includes.

Put the integration boundary where the variation is. One adapter per source keeps each source's quirks and failures out of the core.

A search that waits for every source is only as available as the worst one. Contain failure per source so an outage shrinks the results and leaves the page standing.

A failing source can look like an empty one. Treat a sudden silence from a provider as a possible fault until you know otherwise.

How we engage

You have a system like this one.
Tell us where it stands.

Whether it is failing, not yet built, or about to meet a scale it has never seen, we can tell you what we see.

Start a conversation
mostafa@opulion.dev · Response within 24 hours · By inquiry