Skip to main content
Enterprise Architecture

Modular monolith or microservices? A decision framework for Indian enterprises

Microservices solve organisational problems more often than technical ones. Here is the framework we use with clients to decide honestly — and why most of them start with a well-bounded monolith.

3 min read1005 views

Every second architecture conversation we have starts the same way: "We want microservices." It is rarely a technical requirement. It is usually a reasonable fear — that the current system will not scale, or that one team's change keeps breaking another team's feature.

Those are real problems. Microservices are one solution to them, and frequently the most expensive one available.

What microservices actually buy you

Independent deployability. That is the headline benefit, and everything else follows from it. If four teams can ship on four different days without coordinating a release, you have removed an organisational bottleneck that no amount of clever code structure inside a single deployable will fix.

The second real benefit is independent scaling. If your order-ingestion path handles 400 requests per second while your reporting module handles four, splitting them lets you size infrastructure to each.

What they cost

  • Distributed transactions. An operation that was one database transaction becomes a saga with compensating actions. This is not harder to write; it is harder to reason about at 2 a.m.
  • Operational surface. Service discovery, distributed tracing, per-service dashboards, certificate rotation, contract versioning. Each is manageable. Together they are a full-time platform function.
  • Debugging cost. A single user-visible failure now spans four log streams. Without disciplined tracing from day one, incident resolution time goes up, not down.
  • Latency. In-process calls become network calls. Ten of them in a request path is a real performance budget.
If your team cannot yet operate one service well — with alerting, runbooks and a tested rollback — splitting it into eight will not improve matters.

The framework

We score four dimensions with the client. Any two scoring high is usually enough to justify splitting a bounded context out.

1. Team topology

How many engineers work on this codebase, and are they organised into stable teams with distinct ownership? Below roughly fifteen engineers on one product, the coordination cost microservices remove barely exists. Above forty, it dominates everything else.

2. Deployment coupling pain

Count the releases in the last quarter that were delayed because an unrelated module was not ready. If that number is zero, deployment coupling is not your problem. If it is most of them, it is.

3. Divergent scaling profile

Do parts of the system have genuinely different load or resource shapes? A document-generation worker that needs 8 GB of memory for thirty seconds a day has a different profile from an API serving steady traffic. That is a real signal.

4. Divergent change velocity

A pricing engine that changes weekly and a general-ledger posting module that changes twice a year have different risk appetites. Isolating them means the stable component is not dragged through every risky release.

What we usually recommend instead

A modular monolith: one deployable, but with hard internal boundaries. Each module owns its own tables. No module reaches into another module's schema. Communication between modules goes through an explicit in-process interface — the same interface you would expose over HTTP if you later split it.

src/
  modules/
    sales/          # owns sales_* tables
      api/          # public interface — the only entry point
      domain/
      infrastructure/
    inventory/      # owns inventory_* tables
      api/
      domain/
      infrastructure/
  shared/
    kernel/         # ids, money, dates — no business logic

Enforce it in CI with a dependency-check rule. If inventory imports anything from sales except sales/api, the build fails. That single rule preserves nearly every architectural option you might want later.

The migration path, when you need it

Because each module already owns its data and exposes a narrow interface, extraction is mechanical rather than archaeological:

  1. Replace the in-process interface call with an HTTP or message-based adapter, keeping the same contract.
  2. Move the module's tables to their own schema, then their own database instance.
  3. Deploy the module separately. The calling code never changed.

We have done this for clients three and four years after the original build. It took weeks, not the eighteen-month rewrite that an unbounded monolith would have demanded.

The honest summary

Start with a modular monolith. Enforce the boundaries mechanically. Extract services when a specific, named pain justifies the specific, measurable cost. Architecture is not a status symbol — it is a set of trade-offs you should be able to defend out loud in a room full of people paying for it.

Found this useful?

Keep reading

Related articles

More from the blog

Other articles worth your time

Browse all

Free consultation

Have a system that needs this kind of thinking?

Tell us what you are working on. The first consultation is free, and you will speak to an engineer.