GetMeCab EngineeringGetMeCab.com ↗

Engineering journal · Architecture

Rebuilding GetMeCab: Why We Changed Our Architecture

How GetMeCab is separating transactional ownership, defining safe fallback, and migrating its booking platform in small, reversible steps.

A road bridges a coupled structure and separate buildings, illustrating incremental migration.

A booking request times out. Can the frontend safely retry it through the old backend?

That question captures one of the hardest parts of rebuilding GetMeCab. A failed response does not tell us whether a booking was created. A fallback that appears to improve availability can create a second transaction.

Our architecture had to make that ambiguity explicit: which system owns the operation, what state it may have created, and what the caller is allowed to do next.

GetMeCab helps travellers find a cab, receive a quote, book, and pay. We are moving its coupled legacy platform toward clearer application boundaries through an incremental migration. Three decisions explain the approach.

1. Separate presentation from transactional authority

The original system combined public pages, search behavior, pricing, booking, and payment concerns across a legacy frontend and backend. This made changes difficult to isolate. A URL migration could affect booking handoff; a frontend change could drift into deciding which fare the customer received.

We separated the public experience, Orbit, from the domain API layer, Nexus. Orbit owns pages, navigation, and public URL behavior. Nexus owns pricing, journey, booking, and payment capabilities as they migrate.

The useful boundary is the decision each system can make. The website displays an authoritative quote. It cannot manufacture a substitute price because the quote service is unavailable. A payment success screen must reflect verified payment state; reaching a browser redirect is insufficient evidence.

This gives new customer channels the same contract. A WhatsApp booking flow can consume the domain APIs without becoming another implementation of pricing and payment rules.

The cost is real: network failures, API compatibility, and additional operational work. Those costs are justified only when the boundary prevents business rules from diverging across clients.

Before: public pages, pricing, bookings and payments are coupled. During migration: new and legacy experiences use their respective capabilities, with operational data still shared.

Simplified migration architecture. For each defined traffic cohort, one system owns a transactional capability. Shared data remains part of the transition.

2. Treat fallback as a business decision

Keeping the old system available makes incremental migration possible. It also introduces a dangerous temptation: try the new backend, then call the old one whenever something fails.

Consider the booking timeout again. If the first backend committed the booking before its response was lost, a second backend may create a duplicate. The caller needs to establish the operation's state and follow its retry contract before attempting recovery.

Our migration design therefore selects an owner for each capability within a defined slice of traffic. That slice can depend on market, channel, and page family. The fallback policy must identify when switching is permitted and whether customer or payment state could already exist.

Presentation and transactions also need separate fallback rules. Preserving legacy page content does not grant permission to substitute a legacy fare when the new pricing path fails.

This changes the question in a review from “Does the fallback return a response?” to “Can this fallback preserve the meaning of the operation?”

3. Move the smallest capability we can verify

A single switch for the entire new site would combine too many changes: URLs, content, routing, pricing, checkout, and payments. We organize migration around smaller capabilities and page families so failures have a tractable scope.

Before replacing a public page, we establish what must survive: canonical identity, redirects, route intent, availability, and checkout handoff. Invalid inputs belong in that baseline too. An unknown route must not receive an invented fare simply because the new template can render it.

Commands require a higher bar than reads. Booking and payment migrations must address idempotency, callbacks, retries, and reconciliation. A page rendering correctly cannot establish that those operations are safe.

Our release process uses a specific tested build, a limited traffic cohort, an observation window, and a defined rollback path. Expansion follows evidence from that cohort.

This approach leaves temporary complexity behind: adapters, shared data, routing rules, and legacy paths. Each needs a retirement condition. Otherwise, gradual migration can become permanent duplication.

What we measure—and what remains unfinished

Verification has to follow the customer journey. A healthy page response does not prove that its quote works. A booking does not prove payment, and payment does not prove a completed trip.

We separate operational health from acquisition and commercial outcomes. Missing telemetry remains unknown rather than being interpreted as zero failures. This prevents a technically successful release from becoming an unsupported claim of business improvement.

The migration is ongoing. Legacy dependencies and shared operational data remain; telemetry coverage is still expanding. We cannot infer a performance or revenue improvement from the architecture diagram alone.

The most useful design question has been: who owns this decision when something fails? Answering it gives us a concrete way to evaluate a boundary, a retry, or a migration step. For GetMeCab, that is the foundation for changing the platform while preserving the booking journey customers rely on.