AR2: Architecture styles: monoliths, modular monoliths, microservices and events
The modular monolith
🎯You'll be able to
Explain how a modular monolith keeps one deployable while giving each part clear boundaries, and how those boundaries are enforced.
🏠The everyday picture
The growing restaurant keeps one building but divides the kitchen into stations: tandoor, curries, desserts. Each station has its own tools and passes finished dishes through one counter. A mess at one station stays there.
📘What it really is
-
A modular monolith is still one deployable, but split into modules that each own their own data and expose a small public interface (a "front door", such as
index.js). -
Rules: a module uses others only through their front doors; no module reads another's private records; dependencies don't form cycles.
-
Rules need enforcement, or they erode under deadlines. A checker in the build, such as
npm run lintwithmodule-boundariesin the kit, fails the build when a module reaches inside another. -
Its strengths: most of the monolith's simplicity, with changes contained inside modules; and modules with clean boundaries can be moved out later if needed.
-
Its limit: everything still runs in one process, so a slow part can still hold up others, and every deploy still ships everything.
📝A worked example
Input: the starter's reports/index.js requires ../orders/store and ../payments/ledger.
Steps:
- Add public functions:
orders.listOrders()andpayments.captured(). - Change reports to use them, so it no longer knows how orders and payments keep their records.
- Run
npm run deps: the two internal reaches from reports are gone. - Switch on
module-boundariesso a new reach fails the build.
Expected output: reports depends on orders and payments only through their front doors, and the checker keeps it that way.
🌍Where it's used
- Shopify has described splitting its large Ruby application into components with enforced boundaries rather than into services.
- Banks often structure core systems as modules with strict interfaces inside one platform.
- Small teams use modules to keep a growing codebase understandable.
📝Case scenario
A school platform split its code into modules for fees, students and timetables, but didn't check the rules. Within a year, timetables read fee records "just once" for a report, then twice. Adding a checker to the build found 23 reaches; fixing them took a month, which a checker from the start would have avoided.
🖱️Try it
In the starter, find one internal reach with npm run deps and write the public function that would replace it. Success: the function's
name says what it gives, not how the data is stored.
⚠️Common mistakes
- Folders without rules. Sign: modules in folders, but anything can require anything. Fix: a front door per module, checked.
- A shared "utils" module that grows. Sign: half the code in
common. Fix: move code to the module that owns the idea. - Expecting fault isolation. Sign: "now notifications can't slow orders". Fix: in one process, it still can; that needs AR6.
🛠️For your project
Project 2's test 4 checks notifications' boundaries with the kit's dependency tool. Project 3 switches module-boundaries on for all.
✅Quick check
What does a modular monolith keep from a monolith?
Show the answer
One deployable and one process, with its simplicity.
Why must module boundaries be enforced by a tool?
Show the answer
Without a check, reaches creep in under deadline pressure and the boundaries erode.
