Skip to content

Shopify: building boundaries inside a giant monolith

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

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.

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.

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

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.

  • 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.
  • 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.

Written and edited by Abhinav Taduka, enterprise software architect.