Real architecture · Mid
Shopify: modular monolith (Deconstructing the Monolith)
Real production architecture. Key points: component boundaries and static dependency checking.
Short answer
Real production architecture. Key points: component boundaries and static dependency checking. Shopify's core is one of the largest Ruby on Rails codebases and started out as a monolith: distinct features in the same code, with no boundaries between them. A later retrospective from the team itself shows the scale of the result, with more than 2.8 million lines of Ruby.
01 · CONTEXT
A Rails monolith that grew too big
Shopify's core is one of the largest Ruby on Rails codebases and started out as a monolith: distinct features in the same code, with no boundaries between them. A later retrospective from the team itself shows the scale of the result, with more than 2.8 million lines of Ruby.
While the product is young, the monolith has real advantages: it is the easiest architecture to implement, it concentrates the work in one repository and one test and deploy pipeline, and it lets components call each other directly, without going through service APIs.
The limit appeared in 2016, when it became increasingly hard to build and test new features. Two symptoms marked that point: a seemingly innocent change could cause a cascade of test failures with no apparent relation, and simple changes required a lot of context.
The shipping code lived alongside the checkout code, and there was little to stop one from calling the other.
The article's example: the shipping calculation calls the tax calculation.
Changing the tax calculation could alter the shipping result, without the cause being obvious.
Because of this entanglement, anyone joining the shipping team would end up needing to understand orders and payments too.
02 · ALTERNATIVES
Monolith, microservices, or middle ground
A well-known alternative was microservices, but the team pointed out the cost: multiple pipelines and infrastructure per service, no guarantee of data access, and network calls that add latency and lose reliability. Shopify chose to keep the code in one codebase and to define and respect boundaries between components.
| Monolith without boundaries | Microservices | Modular monolith | |
|---|---|---|---|
| Boundaries between domains | None between features | The network separates the services | Strictly enforced between domains |
| Communication | Direct calls between components, no service API | Crosses the network, with more latency and less reliability | Inside a single application, with code in one place |
| Deployment | One repository and one test and deploy pipeline | Pipelines and infrastructure per service, with coordinated deploys in large refactors | More modularity without increasing the deployment units |
03 · COMPONENTIZATION
Reorganize by domain and isolate dependencies
The first step was organizing the code. It followed the typical Rails pattern, by software concepts; the goal was to reorganize it by real-world concepts, such as orders, shipping, inventory, and billing, that is, by business domains.
The initial list of components came from manually labeling each Ruby class in a spreadsheet. The files were moved in a single pull request generated by scripts. The cost: some of the history on GitHub was lost, because the moves showed up as deletions and creations.
The second step was isolating dependencies. Each component defines a public interface and takes exclusive ownership of its data. An internal tool, Wedge, began to flag the accesses that violate these boundaries.
Each component exposes a public interface and owns its data; access from outside must go through it.
During CI, a tool uses Ruby tracepoints to obtain the code's call graph.
Calls between components go to Wedge, which decides which are acceptable and which violate the boundary.
By Wedge's rule, calls are only accepted for what is explicitly public; on an access like this to another component's internals, Wedge would flag a violation.
With dependencies isolated, the legacy tax engine could be replaced by a new calculation system.
04 · STATIC CHECKING
From the call graph to Packwerk
In the follow-up retrospective, the team said it kept the original ideas but that almost all the details changed. For dependency checking, the team started using a new tool, Packwerk, in place of Wedge.
| Wedge | Packwerk | |
|---|---|---|
| What it analyzes | Calls recorded with the instrumented test suite in CI | Static references to constants |
| Signal quality | Results often not very useful; sometimes it was not even clear which component a call came from | Less ambiguity; explicit references are more actionable |
| Feedback time | More than an hour, because it depended on the full suite | A few minutes on the largest codebase, fitting into the pull request flow |
A change to some component arrives as a pull request.
Packwerk analyzes the static references to constants, which are explicit and less ambiguous.
Because the full analysis takes a few minutes on the largest codebase, it fits into the pull request flow.
Changes that break the dependency graph or encapsulation are rejected before reaching the main branch.
05 · LESSONS
What was still unresolved
The later retrospective brings adoption numbers: the main monolith had 37 components with public entry points, and Packwerk covered about a third of them. The architecture change under way is described as largely a people problem.
Analyzing the graph, the team found a very dense dependency structure, with each component depending on more than half of the others, plus many circular dependencies. This, according to the text, makes it impossible to reason about the components and evolve them separately. To break cycles, the ways out cited were moving responsibilities or applying inversion of control.
The approach chosen for the main monolith followed the vision of Domain Driven Design for defining the components. The acknowledged downside is that almost no component yet has a complete, strong boundary. Insisting on interface consistency added rules, which always bring friction.
Frequently asked questions
What is Shopify: modular monolith (Deconstructing the Monolith)?
Real production architecture. Key points: component boundaries and static dependency checking.
What does the Shopify: modular monolith (Deconstructing the Monolith) design cover?
It is organized in these sections: A Rails monolith that grew too big, Monolith, microservices, or middle ground, Reorganize by domain and isolate dependencies, From the call graph to Packwerk, and What was still unresolved.
How long is Shopify: modular monolith (Deconstructing the Monolith) and what level is it?
It takes about 15 minutes to read and is rated Mid level.
Now design it yourself
Run this case as an interview in the System Design school: you make the decisions, the tutor challenges them and you see how your design scores.