Home December 1, 2023 5 min read By Arunkumar Ganesan

Microservices Are Not the Default Answer

Microservices can be powerful, but only when the team has a real need for independent change, independent scale, and the operational maturity to run a distributed system.

The decision checks

Team ownership Can one team own, deploy, and support the service without constant coordination?
Domain boundary Is the boundary stable enough to become a network contract?
Operating cost Can the team monitor, trace, test, secure, and recover many moving parts?
Clear payoff Does the split improve scale, release speed, or fault isolation enough to justify it?

I do not recommend microservices as the default answer. I use them when independent ownership, scaling, deployment, or data boundaries are strong enough to pay for the operational cost.

Martin Fowler and James Lewis describe microservices as independently deployable services organized around business capability. That is the attraction. A team can own a focused service, change it independently, scale it independently, and choose the technology that fits the problem. But the same architecture also introduces remote calls, service contracts, deployment coordination, observability, failure handling, and data consistency work.

The question is not whether microservices are good. The question is whether the independence they provide is worth the distributed system you are agreeing to operate.

They solve independence problems

When I talk about "They solve independence problems", I am checking whether Microservices Are Not the Default Answer makes ownership, failure handling, or rollback clearer.

Microservices are most useful when different parts of the business need to move at different speeds. A payments capability may need strict compliance and careful release control. A recommendation capability may need fast experiments. A reporting capability may need separate scale. If one deployment unit makes those needs fight each other, service boundaries can help.

They also help when ownership is real. A service should not be just a folder with a network call around it. It should have a team that understands the domain, owns the contract, watches production behavior, and can make decisions without asking five other teams for every change.

They do not remove complexity

My recommendation in "They do not remove complexity" is to write the operational cost beside the architecture.

The most common mistake is thinking that breaking a system into services makes the system simpler. It may make each codebase smaller, but the total system often becomes harder to reason about. A local method call becomes a network call. A transaction becomes an eventual consistency decision. A test becomes a contract, environment, or integration problem.

Research and experience reports on microservice migration repeatedly call out this point: the pattern can deliver flexibility and scalability, but it introduces distribution complexity. If a team does not have strong deployment automation, monitoring, logs, tracing, and incident habits, microservices can make the engineering experience worse before customers see any benefit.

A practical decision method

1
Start with the change pressure Split only where teams or capabilities need to change, deploy, or scale independently.
2
Prove the boundary in a modular monolith If the boundary cannot stay clean in one process, it will not become clean because HTTP is involved.
3
Count the operating burden Add deployment pipelines, alerting, tracing, contract testing, secrets, ownership, and support rotation to the cost.
4
Split gradually Extract one service where the payoff is obvious. Learn from production before repeating the pattern.

When a modular monolith is better

A modular monolith is often the better starting point for new products, small teams, early domains, and systems where most changes still touch the same data and workflow. It keeps the deployment model simple while forcing the team to practice good boundaries inside the codebase.

That is not a step backward. It is a way to delay distribution until the team has better evidence. If the domain stabilizes and one module starts needing different release or scaling behavior, that module becomes a stronger candidate for extraction.

Watch for false signals

What I learnt around "Watch for false signals" is that a clean diagram is not enough if the failure path is vague.

Service count is not an architecture strategy. Neither is copying a large company that solved a different scale problem. A team can have twenty services and still be tightly coupled. Another team can have one deployable application with strong module boundaries, excellent tests, and fast delivery.

The real signals are simpler: can teams change their part without broad coordination, can failures stay contained, can production behavior be understood quickly, and can the data model support the boundaries without constant workarounds?

The balanced answer

Microservices are not the solution for everything because architecture is not a badge of maturity. It is a set of constraints chosen to help the business change safely. Choose microservices when the independence is real and valuable. Choose a modular monolith when simplicity gives the team more speed, clarity, and reliability.

The best architecture is not the one with the most services. It is the one where the team can understand the system, change it with confidence, and operate it without turning every feature into a coordination exercise.

Billing platform example

I use "Billing platform example" to keep Microservices Are Not the Default Answer grounded in a real system, because abstract patterns are too easy to agree with and too hard to operate.

A billing platform is a common place to pause before splitting. If invoice generation, tax calculation, and payment posting change together every sprint, three services may only add network calls and release coordination. Split only when one capability truly needs a different owner, scale profile, or deployment rhythm.

Boundary check code

When I show "Boundary check code", I want the code in Microservices Are Not the Default Answer to reveal the production decision, not just the syntax.

A module boundary test can protect a modular monolith before a service split:

@Test
void billingDoesNotReachIntoInventoryTables() {
  assertThat(billingModule.dependencies())
      .doesNotContain("inventory.internal");
}

The test enforces a module boundary before teams split runtime ownership. If billing reaches into inventory internals, the service boundary is not ready.

What I learnt is that microservices solve organizational and scaling problems, but they also charge interest through networking, observability, testing, and coordination.

#Microservices #SoftwareArchitecture #DistributedSystems #EngineeringLeadership #ModularMonolith