Style Selection: Monolith, Modular Monolith, Services
What This Concept Is
An architectural style is the coarse shape of the system: how many runtime units there are, how they communicate, and what the unit of deployment is. For a 6-week solo capstone, the realistic choices reduce to three:
- Monolith. One deployable unit. All code in one repo, one process, one deploy. Internal modules communicate via function calls; there are no enforced internal boundaries.
- Modular monolith. Still one deployable unit, but the code is organized into well-separated modules with enforced boundaries (no cross-module reaching into internals), and each module could be extracted later. This is the capstone default.
- Services (microservices). Multiple deployable units. Modules communicate via network -- HTTP, gRPC, queues. Each can be deployed and scaled independently, at the cost of real operational overhead.
For a solo 6-week capstone, the default is modular monolith. Services are almost always the wrong choice at this scale; a straight monolith is fine for the simplest capstones but leaves you with no DDD demonstration and a diagram that collapses into one box. The style decision is the most-asked-about decision in the defense; you should be able to answer "why this style?" in one sentence tied to your top-3 characteristics.
Why It Matters Here (In the Capstone)
Style selection determines how much of your six weeks is spent on code versus infrastructure. A services architecture eats weeks on service discovery, inter-service auth, retry logic, distributed tracing, and local dev environments. A modular monolith gives you clean DDD-shaped boundaries in one process and lets you spend those weeks on the core subdomain (concept 05).
The second reason: the style decision is the one decision that is most visible in the C4 Container diagram (concept 11). Modular monolith = one application runtime. Services = multiple. The grader can see the style from ten feet away. An over-complicated topology looks unearned if the problem doesn't need it; a too-flat topology looks unambitious.
The third reason: style is a one-way door in the capstone time budget. You can switch from monolith to modular monolith in a day by imposing module boundaries. You cannot switch from services back to a monolith without a week of merging. This asymmetry is why the capstone default is modular monolith -- the easiest shape to evolve toward services later, and the easiest to keep simple if services turn out unnecessary.
Concrete Example(s) -- from a real capstone
Example A -- inventory service (drivers: correctness, operability, offline availability):
- Alternative 1: Monolith. Single Node/Python process. Pro: fastest. Con: no visible DDD boundaries; the event-log core would mix with reconciliation and admin reporting in one tangle.
- Alternative 2: Modular monolith. Single process. Modules for
scan-core,reconciliation,admin-ui,persistencewith enforced boundaries. Pro: one deploy, one observability story, enforced boundaries demonstrate DDD. Con: discipline needed to enforce boundaries. - Alternative 3: Services. Scan-core as one stateful service, reconciliation as a batch job, admin-ui as a separate SPA+BFF. Pro: isolates the high-integrity scan path. Con: doubles the deploy; operability takes a hit; offline reconciliation is harder because the network is the primary seam.
- Chosen: Modular monolith. Reasoning: correctness and operability both favor fewer moving parts; offline availability only needs the client to be offline, not a distributed server. Boundaries enforced by an import-graph fitness function (concept 09).
Example B -- ticketing platform (drivers: seat-cap correctness, time-to-event-day, availability-during-window):
- Monolith. Pro: trivial. Con: the "seat cap" invariant gets lost in CRUD noise.
- Modular monolith. Pro: reservation module has a narrow public API (
reserveSeat,releaseSeat); QR issuance and door-scan live behind their own modules. Shared DB enforces the cap transactionally. - Services. Pro: door-scan could be isolated for peak load. Con: for a 500-person event, peak load never needs it; the transactional cap is harder across services (distributed transactions or sagas for a capstone is obviously wrong).
- Chosen: Modular monolith. Seat-cap correctness and modular monolith both want a single transactional store and a narrow module API; services would buy nothing and cost a week.
Example C -- finance aggregator (drivers: correctness, privacy, simplicity):
- Monolith. Strong candidate because privacy pushes against any network seam. Con: even here, a little modularity prevents the rules engine from bleeding into the import pipeline.
- Modular monolith. Pro:
import,rules,reportas modules with an event-y boundary (raw rows -> categorized transactions -> reports). Everything local; no network. Con: almost none -- modularity is nearly free in one process. - Services. Ruled out by privacy: any network seam is a risk. Also, for one household there is literally no scale problem to solve by splitting.
- Chosen: Modular monolith. Privacy makes "one process on one laptop" the only defensible runtime shape.
Notice the structure: three alternatives, each with pro/con tied to the drivers, a chosen option, and a paragraph justifying why. That paragraph goes straight into ADR-001.
Common Confusion / Misconceptions
- "Microservices are the modern choice." Microservices are the right choice for problems you do not have at capstone scale. Ops, observability, debugging, and local dev costs dominate the benefits at 1 user or 1000.
- "Monolith is old-fashioned." The industry moved back toward modular monoliths. Shopify, GitHub, Basecamp, Stack Overflow run (or ran) monoliths at scales that dwarf your capstone. A modular monolith that ships is better than a services architecture that doesn't.
- "I'll start with services so I can scale later." You are writing a capstone, not pitching to a VC. The strangler-fig pattern (extract-from-monolith-when-needed) is far cheaper than the opposite direction. Start in the shape that matches today's drivers.
- "Modular monolith is just a monolith with folders." It is more: it is a monolith with enforced boundaries. Importing from another module's internals must break the build or fail a lint rule. Folders alone give you a folderolith -- it looks modular until it isn't.
When Services Are Justified at Capstone Scale
There are narrow cases where a second runtime is actually earned:
- The capstone's subject matter is distributed systems -- you need services to demonstrate what you claim to have learned.
- Genuine heterogeneity that cannot live in one process: a Python ML component next to a Node.js web tier; a long-running GPU worker next to a stateless API.
- A hard isolation requirement (security, compliance, or a hostile-input boundary) that cannot be achieved by in-process modules.
Even then, keep the count to two runtime units, not seven. The second process is there to demonstrate a specific learning, not to prove you can operate Kubernetes.
Enforcing Module Boundaries Mechanically
For a modular monolith the whole claim rests on the boundaries. Enforce them in at least one way:
- A lint rule banning cross-module imports into
/internalfolders (ESLintno-restricted-imports, Pythonimport-linter, Java ArchUnit, Go internal packages). - A test (this becomes a fitness function; see concept 09) that scans the import graph and fails on disallowed edges.
- Separate package/project boundaries inside a monorepo so the language's own import resolution rejects forbidden imports.
Pick one. Document it in the design doc's architecture section. If you do not enforce it mechanically, "modular monolith" becomes "a monolith I intended to keep modular."
How To Use It (In Your Capstone)
- Write the three alternatives (monolith, modular monolith, services) in your design doc, even the ones you "know" you won't pick.
- For each, list one pro and one con tied directly to your top-3 characteristics. Drivers are the currency of this comparison.
- Pick one. Default to modular monolith unless you have a written reason to deviate.
- Write a one-paragraph justification tied to the drivers. This paragraph becomes ADR-001 (concept 12).
- Name the enforcement mechanism. Lint rule, import-graph test, or package boundary -- pick one and commit to it this week.
- Update the C4 Container diagram to show the chosen style; one runtime box for modular monolith, multiple for services.
- Add the boundary fitness function to CI in the same PR as the first module split. Do not defer.
See also (integrative)
Style selection integrates the pattern catalog from S7 M02, the distributed-systems reality from S6 M05, and the cost-of-microservices argument from S8 M02.
S7 M02 -> Modular monolith: the right default for most systems-- use when you need the full argument for why modular monolith is the default, including enforcement options.S7 M02 -> Enforcing module boundaries in code-- use when picking the mechanical enforcement. Menu of options with trade-offs.S7 M02 -> Style selection by architectural characteristics-- use when you want the full-cluster version of the decision framework.S8 M02 -> The microservices distillation / the cost model-- use when tempted by services. This is the "read this before doing it" module.S6 M05 -> The eight fallacies of distributed computing-- use when "let me just add one small service" sounds harmless. Every fallacy is a capstone risk you just bought.
External references (curation-validated this session):
- Monolith First -- Martin Fowler -- use when you need the canonical argument that even at scale, start with a monolith; capstones are especially in-scope.
- Microservices -- Martin Fowler -- use when calibrating what microservices actually mean industrially (not just "multiple services").
- Under Deconstruction: The State of Shopify's Monolith -- use when you need a real-world example of modular monolith at scale to justify the capstone choice.
- Inside Shopify's Modular Monolith -- Techworld with Milan -- use when wanting concrete examples of enforcement mechanisms used in a real codebase.
- Patterns of Distributed Systems -- Martin Fowler -- use when you've decided you need services anyway. Read one pattern (e.g., Consistent Core); the complexity cost will usually reverse the decision.
Check Yourself
- State your chosen style in one sentence.
- What was the most attractive alternative you rejected, and what was the one fact that rejected it?
- Which of your top-3 characteristics most strongly pushed the decision?
- How will module boundaries be enforced mechanically? Name the specific tool/test.
- If a grader probes "why not services?", your answer is one sentence tied to which driver?
- If you picked services, what is the count (2? 5?), and what justifies each beyond the second?
Mini Drill or Application (Capstone-scoped)
- Drill 1 (25 min). Write the three-alternatives passage exactly as in the examples above. Put it in your design doc under "Architectural Style." Do not skip the rejected options.
- Drill 2 (20 min). Set up one enforcement mechanism. ESLint rule, import-linter, ArchUnit -- whatever fits your stack. Commit a test that fails on a deliberate violation.
- Drill 3 (10 min). Draw the Container diagram (concept 11) in a way that visibly shows your chosen style. If it looks like every other capstone's diagram, you haven't shown a choice.
- Drill 4 (15 min). Write ADR-001 (architectural style) now, reusing the justification paragraph. This is the first of the three must-write ADRs.
- Drill 5 (defensive, 10 min). Rehearse the 30-second defense of your style choice out loud. If you hesitate, the justification paragraph is not yet sharp.
Source Backbone
Capstone design applies earlier architecture and domain material. These books are the source backbone for the decisions in this module.
- Fundamentals of Software Architecture - architecture characteristics, styles, and tradeoffs.
- Learning Domain-Driven Design - domain discovery, subdomains, and bounded contexts.
- Clean Architecture - dependency direction and boundary discipline.
- API Design Patterns - contract and API decision support.