Shopify: building boundaries inside a giant monolith
At a glance
Section titled “At a glance”| Organization | Shopify |
| Period | 2016 to 2019 |
| Problem | A decade-old Rails monolith with over 1,000 contributors had become fragile and slow to change |
| Architecture | One codebase and one deployment, divided into business components with public APIs and owned data |
| Patterns | Modular monolith, domain-driven design |
The problem
Section titled “The problem”Shopify’s core application was one of the largest Ruby on Rails codebases in existence, built over ten years by more than 1,000 developers. Around 2016 it started to hurt:
- Fragile code. Small changes broke unrelated tests, because domains were tightly coupled. A tax change could affect shipping rates.
- Slow, hard-to-write tests.
- Hard onboarding. New developers had to understand many unrelated parts of the system before shipping a feature.
Why not microservices
Section titled “Why not microservices”The team looked at splitting the application into services and decided against it. Each service would need its own test and deployment pipeline and infrastructure; data would not always be available where it was needed; network calls would add latency and failure modes; and large refactors across services would need coordinated deploys.
What they did
Section titled “What they did”They formed a componentization team in early 2017 and kept one codebase and one deployment, but made the boundaries inside it real:
- Organize by business domain. Code moved from Rails’ technical layers (models, views, controllers) into components such as orders, shipping, inventory and billing, each structured like a small Rails app. About 6,000 classes were assigned to components, and the move was done in one large, scripted change.
- Public APIs and owned data. Each component exposes a public interface and is the only owner of its data.
- Measure violations. An internal tool called Wedge traced calls during the test suite, built a graph of calls between components, flagged boundary violations and gave each component an isolation score.
flowchart LR
subgraph mono [One Rails application, one deployment]
orders[Orders] -- public API --> billing[Billing]
orders -- public API --> shipping[Shipping]
shipping -- public API --> inventory[Inventory]
tax[Tax] -. violation flagged by Wedge .-> shipping
end
Results
Section titled “Results”Shopify reported early benefits while isolation work continued. Its clearest example: replacing the old tax engine with a new tax calculation system, which the team said would have been nearly impossible before components were isolated. The post gave no quantitative metrics.
Trade-offs
Section titled “Trade-offs”- Boundaries need enforcement. In one codebase it is always easy to call into another component; Shopify needed tooling to keep boundaries honest.
- One deployment unit. Components can’t be scaled or released independently.
- The big move lost history. Git saw the reorganization as deletions and additions, which broke file history.
Lessons you can reuse
Section titled “Lessons you can reuse”- Try modular boundaries inside your monolith before splitting it into services. You get most of the design benefit without the operational cost.
- Organize code by business capability, not by technical layer.
- Make boundaries measurable, with a tool or architecture tests, or they will erode.
- Refactor when delivery slows, not before you understand the domain.
Related on ArchitectureCompass
Section titled “Related on ArchitectureCompass”- Modular monolith
- Microservices vs modular monolith
- Uber organizes services into domains, the same idea applied to microservices
Sources
Section titled “Sources”- Kirsten Westeinde, “Deconstructing the Monolith: Designing Software that Maximizes Developer Productivity” (opens in a new tab), Shopify Engineering, February 2019
Written and edited by Abhinav Taduka, enterprise software architect.