Architecture Case Study

🚗

Uber: From Monolith to Microservices to DOMA — A Three-Act Architecture

How Uber's architecture evolved through three distinct phases — and why their microservices decomposition almost broke the company before DOMA saved it.

MicroservicesDomain ArchitectureOrganizational ScalingService Mesh

The System

Uber processes over 20 million trips per day across 10,000+ cities in 70+ countries. The platform must coordinate real-time matching between riders and drivers, calculate dynamic pricing based on supply and demand, process payments in dozens of currencies, and provide accurate ETAs — all within seconds.

Uber's architecture has undergone three major evolutionary phases, each driven by different constraints and each teaching different lessons about the relationship between organizational growth and system design.

The Constraints

1. Hyper-growth. Uber grew from a small San Francisco startup to a global company operating in thousands of cities within a few years. The engineering team grew from dozens to thousands of engineers in the same period. No architecture designed for 50 engineers survives contact with 5,000 engineers unchanged.

2. Real-time coordination. Ride-hailing requires sub-second decision-making: matching a rider with the optimal nearby driver, calculating an accurate ETA, adjusting prices based on current demand — all in real time. Batch processing is not an option for the core matching loop.

3. Multi-city, multi-currency operations. Each city has different regulations, payment providers, tax rules, and traffic patterns. The architecture must support localization without creating per-city forks of the codebase.

4. Safety and trust. Uber connects strangers in cars. Every architectural decision around identity, payment, and trip tracking has direct implications for physical safety.

The Architecture — Three Acts

Act 1: The Monolith (2010–2014)

Uber's original architecture was a single Python monolith called "Uber" (creatively named). It handled everything: dispatch, payments, trip management, driver management, and the API layer.

This worked brilliantly for the first few years. A single codebase meant:

  • Any engineer could understand the entire system
  • Features could be built quickly without cross-team coordination
  • Deployment was a single artifact
  • Debugging was straightforward — one process, one log stream

But as the team grew past 200 engineers, the monolith became a bottleneck:

  • Merge conflicts: Multiple teams editing the same files
  • Test suite slowdown: The test suite took over an hour
  • Deployment risk: A bug in the payment code could take down the dispatch system
  • Scaling constraints: The entire monolith had to scale together, even though dispatch was CPU-bound and payments were I/O-bound

Act 2: Microservices Explosion (2014–2018)

Uber responded by aggressively decomposing the monolith into microservices. Each team was empowered to create and own their own services. The result: by 2018, Uber had over 4,000 microservices.

The decomposition initially felt liberating:

  • Teams could deploy independently
  • Services could scale independently
  • Technology choices could be made locally (some teams used Go, others Java, others Node.js)

But the explosion created new, more insidious problems:

The Dependency Web: With 4,000 services, the dependency graph became incomprehensible. A single rider request might traverse 70+ services. No single engineer understood the full call chain.

Cascading Failures: When one of those 70 services had a latency spike, it propagated through the entire chain. The blast radius of a single service degradation was unpredictable and often system-wide.

Inconsistent Data Models: Different teams created different representations of the same domain concepts. There were multiple definitions of "trip," "user," and "payment" across different services, leading to data inconsistencies and integration nightmares.

Organizational Chaos: The freedom to create services without guidance led to service sprawl. Teams created services for tiny functions that could have been libraries. The operational burden of managing 4,000 services overwhelmed the platform team.

Act 3: DOMA — Domain-Oriented Microservice Architecture (2018–Present)

Uber recognized that unbounded microservice decomposition was as dangerous as a monolith — just in different ways. They introduced DOMA (Domain-Oriented Microservice Architecture), which adds organizational structure to the microservices chaos.

DOMA organizes services into domains — collections of related services that represent a single business capability:

┌─────────────────────────────────────────────┐
│                DOMA Structure                │
├─────────────────────────────────────────────┤
│                                             │
│  ┌─────────────┐  ┌─────────────┐          │
│  │ Marketplace  │  │  Payments   │          │
│  │   Domain     │  │   Domain    │          │
│  │ ┌─────────┐  │  │ ┌─────────┐ │          │
│  │ │Matching │  │  │ │Charging │ │          │
│  │ │Pricing  │  │  │ │Payouts  │ │          │
│  │ │Supply   │  │  │ │Invoicing│ │          │
│  │ └─────────┘  │  │ └─────────┘ │          │
│  │    Gateway   │  │   Gateway   │          │
│  └─────────────┘  └─────────────┘          │
│                                             │
│  ┌─────────────┐  ┌─────────────┐          │
│  │   Maps      │  │   Safety    │          │
│  │   Domain    │  │   Domain    │          │
│  │ ┌─────────┐ │  │ ┌─────────┐ │          │
│  │ │Routing  │ │  │ │Identity │ │          │
│  │ │ETA      │ │  │ │Trust    │ │          │
│  │ │Geocoding│ │  │ │Incident │ │          │
│  │ └─────────┘ │  │ └─────────┘ │          │
│  │   Gateway   │  │   Gateway   │          │
│  └─────────────┘  └─────────────┘          │
└─────────────────────────────────────────────┘

Key DOMA principles:

  1. Domain Gateways: Each domain exposes a single gateway service that serves as the public API for that domain. Internal services within the domain communicate freely, but cross-domain communication must go through the gateway. This dramatically reduces the coupling surface.

  2. Layer classification: Services are classified into layers — infrastructure, business logic, product, presentation — with strict dependency rules. Lower layers cannot depend on upper layers.

  3. Domain ownership: Each domain is owned by a single team (or team of teams). That team has full autonomy within the domain but must maintain the stability of the gateway contract.

The Trade-offs

Monolith → Microservices:

  • Gained: Independent deployment, scaling, and team autonomy
  • Lost: System comprehensibility, operational simplicity, data consistency

Microservices → DOMA:

  • Gained: Bounded complexity, clear ownership, reduced coupling surface
  • Lost: Some team autonomy (must conform to domain boundaries and gateway contracts)

Overall lessons in trade-offs:

  • The number of services is not a measure of architectural quality
  • Decomposition without organizational structure creates chaos
  • The right granularity depends on the organization's size and maturity

The Lessons

1. Architecture must evolve with the organization. What works for 50 engineers (monolith) doesn't work for 500 (microservices), and what works for 500 doesn't work for 5,000 (DOMA). There is no "final" architecture — only the architecture that fits your current organizational reality.

2. Unbounded decomposition is as dangerous as no decomposition. Splitting a monolith into 4,000 services without clear domain boundaries trades one set of problems (coupling within a monolith) for another (coupling across a distributed system). The latter is often harder to diagnose and fix.

3. Gateways are the key to sustainable microservices. By forcing cross-domain communication through a single gateway, DOMA creates a clear, stable interface that can be versioned, monitored, and evolved independently. Internal service changes within a domain don't ripple across the entire system.

4. Conway's Law is not optional. Uber's microservice explosion was partly caused by organizational growth without architectural governance. Teams created services because they could, not because they should. DOMA re-established the alignment between team boundaries and service boundaries.

5. The second system effect is real. Going from monolith to microservices felt like progress — and it was, initially. But the pendulum swung too far. The wisdom is in finding the middle ground: structured decomposition that balances autonomy with coherence.

Credits & References

  • Adam Gluck: "Introducing Domain-Oriented Microservice Architecture" — the Uber Engineering blog post that introduced DOMA to the industry.
  • Building Microservices by Sam Newman: The foundational text on microservice decomposition that influenced Uber's initial approach.
  • Team Topologies by Skelton & Pais: On aligning team boundaries with architectural boundaries — the principle that DOMA formalized.
  • "Microservices: A Definition of This New Architectural Term" by Martin Fowler & James Lewis: The original description that launched the microservices movement.