Custom Software · SaaS

Microservices Are Not Always Better: When CTOs Should Choose a Modular Monolith

Mahesh Kanna

Jump to section

Share

Summarize with AI

The Architecture Choice Is About Change Boundaries

Microservices are not an upgrade from monoliths. They are a trade: independent lifecycle in exchange for distributed failure modes.

Why Microservices Feel Like Progress

Teams assume more services means more maturity. If domain language and team ownership are not stable, splitting services turns one ambiguous model into several networked ambiguous models.

For modular monolith architecture, a senior review should ask which delivery decision is being made, which evidence proves it, and what signal would force the team to pause.

The Split That Made Delivery Slower

A ten-engineer SaaS team split billing, entitlement, account, and notification services before subscription language stabilized. A retry from entitlement duplicated feature access after billing rolled back a payment. Support saw unpaid, product saw premium, finance saw pending. The team later pulled billing and entitlement into a modular monolith with package boundaries and one transaction, then extracted tax later when compliance justified isolation.

Design for Module Ownership First

Premature split:
AccountSvc --> BillingSvc --> EntitlementSvc
    |             |                |
  db\_a          db\_b             db\_c

Modular monolith:
Application
  +-- account module
  +-- billing module
  +-- entitlement module
Explicit interfaces, one deployable

Where Modular monolith architecture Control Belongs

Where Delivery Teams Borrow From the Future

Resume-driven architecture

Teams split services because conference slides say so. They now debug timeouts between two services that always deploy together anyway.

Resume-driven splits also inflate local development cost. Ten engineers now run five containers, five databases, and a mesh they barely understand. Feature work waits on "works on my machine" issues that a single-process monolith would not have surfaced.

Boundaries drawn around org chart, not domain

"Frontend team service" and "backend team service" split chatty APIs without stable domain language. Data duplicates across services.

Org-chart services encourage duplicate models of the same entity. Customer exists in three schemas with slightly different field names. Synchronizing them becomes a permanent tax. Domain modules inside a monolith force a shared language before you pay the network tax to disagree.

Ops maturity lag

Kubernetes and service mesh appear before basic observability and CI discipline exist. Incidents take longer because nobody knows which service owns the bug.

Distributed systems multiply unknowns: retries, timeouts, circuit breakers, and version skew between callers and callees. If your team does not yet run blameless postmortems on monolith incidents, microservices will not magically create maturity. They will distribute the same habits across more dashboards.

Premature data splitting

Two databases for one aggregate root guarantees sync pain. Modular monolith keeps one database until bounded context is proven.

Shared databases are not a moral failure. They are a tool. The goal is clear module ownership of tables and transactions, not premature physical isolation. Split the database when compliance, scale, or team autonomy truly requires independent schema lifecycles, not when a diagram looks cleaner with more boxes.

Project Shape Versus Product Shape

Bad: microservices day one for ten engineers

OrderService <--> PaymentService <--> InventoryService
     |                  |                    |
   DB A               DB B                 DB C
(choreography, sagas, partial outages for MVP)

Good: modular monolith first

Monolith
 ├── order module (public API)
 ├── payment module
 └── inventory module
Single DB per aggregate where needed; modules call internal interfaces

Later, extract payment when:

  • Payment team releases on different cadence, or
  • Payment scale or compliance isolation requires separate deploy, or
  • Clear API and data ownership already stable for quarters

Extraction is a planned surgery, not day-one default.

Extraction should preserve the public API your other modules already use. If internal interfaces were clean, moving a module to its own deployable is tedious but predictable. If modules reached into each other's tables, extraction is a rewrite with extra network hops.

What Changes When Slices Are Real

Modularity is about change isolation, not container count.

A well-structured modular monolith can evolve into services. A poorly structured microservices cluster is a distributed monolith: all services must deploy together, worst of both worlds.

CTOs should fund module discipline (lint rules, internal APIs, domain tests) before funding new clusters.

Module discipline includes architectural tests that fail CI when a billing import reaches into order internals. Those tests are cheaper than production incidents from accidental coupling, and they keep your options open when a real split becomes justified.

Practical decision table

SignalLean modular monolithConsider splitting service
Team sizeOne product teamMultiple teams, different roadmaps
Domain clarityStill discovering languageStable bounded contexts
Scale profileUniform loadOne component needs 10x scale
ComplianceShared controls OKHard isolation required
Release cadenceSingle trainIndependent deploy mandatory
Ops maturityBasic CI/CDTracing, SLOs, on-call per service

When in doubt, monolith module until two rows strongly favor split.

Worked Example: SaaS billing

Ten-person SaaS team splits billing microservice early. Every feature touches subscription state across three deployables.

They reunite logic into a billing module inside monolith, keep public API stable, and extract only the tax engine later when compliance isolation required separate deploy.

Incident time dropped because stack traces stayed in one process.

They also stopped debating distributed sagas for flows that always completed in one user session. The tax engine extraction happened later with a clear API boundary and a compliance driver, not because someone redrew the architecture diagram for a ten-person team.

Where This Shows Up: SaaS and fintech

SaaS: early tenants and features change fast. Modular monolith preserves transaction simplicity for billing and entitlements until boundaries stabilize.

Subscription changes, seat counts, trials, and feature flags often touch the same aggregate in early SaaS. Splitting those across services before the language stabilizes means compensating transactions for operations that should still be one ACID commit.

Fintech: some components (ledger) may need isolation early, but not every screen warrants a service. Split where regulation and scale demand, not by default.

Ledger and payments rails sometimes need hard isolation for audit and blast radius. Customer onboarding screens do not. A modular monolith can host both while the ledger module follows stricter deploy and access rules until it earns its own runtime boundary.

When Services Are Worth the Cost

A simple monolith is acceptable for tiny domains. A modular monolith is the best default for one product team learning the domain. Microservices are justified when scale, compliance, or independent team lifecycle clearly outweighs network cost.

In modular monolith architecture, the alternative paths are not steps on a ladder. Each one carries a different mix of risk, cost, and learning. The weak choice is the one that hides the tradeoff until users, operators, or auditors discover it for you.

The Cost of a Modular Monolith

The question is which pain should arrive first: module coupling inside one process or partial failure across the network. Most young systems have domain confusion before independent scale.

For modular monolith architecture, the useful review is not a generic architecture checklist. It should inspect slice scope, ownership, guardrails, support path, rollback, and defer list. If those fields are missing, the team may still be busy, but leadership does not yet have a decision-quality artifact.

Review Packet Before Release

For modular monolith choice, the release review should prove that the first useful slice can be operated, not merely demonstrated. Start with the vertical path. A real path begins with a user action, crosses identity and authorization, persists state, touches at least one meaningful integration when integration is part of the value, emits audit or telemetry, and has a rollback or correction path.

The second review item is ownership of decisions. One product owner must be able to cut scope without committee escalation. One engineering owner must be able to defend guardrails such as tenancy, data storage, integration style, and deploy discipline. When ownership is diffused, engineering fills the vacuum with assumptions. Those assumptions become expensive once customers and operators adapt to them.

The third item is change safety. The release should include smoke tests, a small set of acceptance tests around the slice, basic observability, and a runbook for the top failure modes. In SaaS, that usually includes tenant isolation, billing or entitlement behavior, and feature flag rollback. In healthtech, it includes PHI handling, audit events, and timeout behavior for external systems. In retail or manufacturing, it includes integration contracts with POS, ERP, warehouse, or plant systems.

Finally, review what was intentionally not built. A disciplined first release has explicit exclusions. Those exclusions matter because they prevent the team from treating unfinished adjacent workflows as bugs after launch. Clear scope is not anti-agile. It is how learning survives contact with production.

Failure Modes to Rehearse

Custom software release tests should cover authorization gaps, tenant boundary mistakes, integration timeouts, duplicate submissions, migration rollback, audit event omissions, and manual correction paths. Test the awkward operator workflow, not only the polished user workflow. If support cannot repair a failed transaction without database access, the release is not operationally ready.

For early slices, test what was deliberately excluded. A narrow release often fails when adjacent workflows sneak in through user behavior. If only one location, payer, plant, or tenant is in scope, the system should reject or route the others intentionally rather than failing in undefined ways.

The Rule for Service Boundaries

Microservices are not an upgrade from monoliths. They are a trade: independent lifecycle in exchange for distributed failure modes. The practical lesson is to demand evidence that fits modular monolith architecture, not a universal checklist. The artifact should expose slice scope, ownership, guardrails, support path, rollback, and defer list clearly enough for another team to challenge the decision.

If modular monolith architecture is the decision in front of your team, use the Sprint Readiness Review to pressure-test the boundary before it hardens.

Sprint Readiness Review

Sample sprint outline + backlog slice from your brief.