PATTERN Cited by 1 source
Two-monolith architecture¶
Problem¶
A product has two fundamentally different workload shapes that don't fit one monolith well:
- Real-time / event-driven. High concurrent connections, data streaming, low-latency reactive. (e.g., location tracking + dispatch at ridesharing; presence + message delivery at chat.)
- Request/response business logic. Standard CRUD with moderate concurrency, multiple domain owners, need for transactional semantics. (e.g., authentication, pricing, payments.)
Forcing both into one monolith picks one runtime/framework that's suboptimal for the other. Splitting straight to a full microservices fleet is premature — the team is still small, the domain boundaries aren't well-understood yet.
Solution¶
Split the system into exactly two monoliths, each picked for its workload shape:
- Real-time monolith — a language/runtime with async + event-loop strengths (Node.js was the classic 2011–14 pick; Elixir/Go today).
- Business-logic monolith — a language/framework with good data-modeling + transactional-DB ergonomics (Python/Django, Ruby/Rails, Java/Spring).
Each monolith has its own database matched to its workload:
- Real-time monolith → Redis or MongoDB for low-latency state.
- Business-logic monolith → PostgreSQL or MySQL for relational invariants.
Optional: a resilience shim between the two monoliths so failures in one don't propagate.
Canonical instance — Uber 2011¶
"The resulting architecture was two services. One built in Node.js ('dispatch') connected to MongoDB (later Redis) and the other built in Python ('API') connected to PostgreSQL. And to improve the resiliency of Uber's core dispatching flow, a layer between dispatch and API known as 'ON' or Object Node was built to withstand any disruptions within the API service."
— sources/2024-03-14-highscalability-brief-history-of-scaling-uber
- dispatch (Node.js + MongoDB→Redis) for rider-driver matching and real-time location.
- API (Python + PostgreSQL) for business logic (auth, promotions, fare calculation).
- Object Node / "ON" between them for failure isolation.
The pattern enabled Uber's engineering org to scale from <20 engineers to ~100 before the next architectural phase (the 2013 SOA split of the API monolith).
Why this is a stepping stone, not a destination¶
Two monoliths share all the monolith-scale pathologies — they just share them in two places instead of one:
- Both eventually grow too large; one or both needs to split further (Uber's API became ~100 microservices in 2014).
- The resilience shim (like Object Node) is itself a monolith that needs to be maintained.
- Team ownership maps to two monoliths → hard to grow past two large engineering teams.
The value is buying time — keeping operational simplicity in the early-scale phase where the full microservices tax doesn't pay off yet.
When to use¶
- Early-stage product with a clear workload bifurcation (real-time vs. request-response).
- Engineering org size < ~50–100.
- Strong language/runtime preferences on either side of the split.
When not to use¶
- Workload isn't bifurcated — a single monolith is cheaper.
- Already at microservices scale and headed there — skip this intermediate step.
Related¶
- concepts/service-oriented-architecture — where this pattern leads when the business-logic monolith grows too big.
- concepts/microservices-migration — the transition from two monoliths to many services.
- systems/uber-dispatch — Uber's dispatch monolith (the real-time side of the 2011 split).
- systems/nodejs — canonical real-time runtime.
- companies/uber — the textbook case study.