Microservices #1 best practice: start monolithic

George Bidilica
George Bidilica · · 5 min read

Microservices don’t make systems scale. They make teams coordinate differently. Early on, that trade-off slows you down more than it helps.

The first cost of microservices isn’t infrastructure. It’s needing agreement before making changes:

  • Contract negotiation: services need to agree on request/response shapes before either side can ship
  • Schema ownership: who owns the data, and who’s allowed to change it?
  • Cross-service migrations: a field rename that used to be a refactor is now a versioned rollout
  • Version compatibility: consumers and producers need to handle old and new formats simultaneously
  • Rollout choreography: deploying service A before service B breaks things, but the dependency isn’t documented
  • Partial failure handling: service B is down, does service A retry, degrade, or fail?

All of that exists before you’ve solved a single scaling problem.

Boundaries you draw too early become walls

Early systems are learning tools. A monolith lets you discover where the real boundaries are before you commit to them.

In a car marketplace, you prematurely separate listings from sellers because they look like different entities. Then you realise that listing visibility depends on seller verification status, subscription tier, and region. Every listing query needs seller data. You’ve put a network boundary through what is effectively one read path, and now every page load is a cross-service call.

Compare that with image processing for vehicle photos: CPU-intensive, independent lifecycle, no shared state with the rest of the system. That’s a boundary the monolith reveals naturally, the photo pipeline slows everything else down and has no coupling to the transaction path.

Don’t guess where to cut, let the pain show you. The cost of drawing boundaries in the wrong place is proportional to how permanent you made them. A module boundary is cheap to move. A service boundary is not.

Split wrong and you get the worst of both

Teams split services without splitting ownership, without separating data, without defining contracts. The services are technically independent but functionally coupled. Every change still requires coordination, but now you also need network calls, retry logic, and distributed tracing to understand what happened.

That’s a distributed monolith. You pay the coordination cost of microservices and get none of the independence. In a car marketplace, the listings service can’t deploy without the search service because they share a database. The payments service can’t change its schema because the reservations service reads from it directly. Three services, zero autonomy.

Splitting code is easy. Splitting ownership is not. Teams often extract services but keep shared databases, shared migrations, shared deploy cadence, and shared developers. The architecture moved, the organisation didn’t. Microservices only help when both move together.

If splitting doesn’t give teams the ability to deploy and evolve independently, it hasn’t solved anything. It’s added ceremony to the same coupling.

One process, one debugger, one picture

A request in a monolith takes place in a single process. You can follow it from entry to exit, set a breakpoint anywhere, and see the full picture. That’s not just convenient, it’s how your team learns the domain.

With microservices, the same request crosses network boundaries. The bug could live in any service, in the contract between them, or in the timing of their interaction. Debugging requires distributed tracing, log correlation, and an understanding of how services communicate. A new developer studying the reservation flow needs to read three repositories, two message schemas, and a deployment diagram before they can follow what happens when a buyer clicks “Reserve.”

A monolith also lets domain knowledge accumulate in one place. The team builds intuition about which parts of the system are coupled, which parts change together, and which parts have genuinely different scaling needs. That intuition is what makes a future split successful instead of premature.

Ownership diverges before code should

A service boundary is not where the code splits. It’s where responsibility splits.

If two teams need to change the same service every week, that service boundary doesn’t exist yet, no matter what the architecture diagram says. If one team can evolve a component independently for months without coordinating with anyone, the boundary already exists whether you formalised it or not.

In a car marketplace, the search team might own indexing, ranking, and query parsing. They deploy weekly, never touch the listings write path, and their scaling needs are completely different. That’s a real boundary. Meanwhile, listings and seller verification are changed by the same three developers, deployed together, and break together. Drawing a service line between them creates a contract where a conversation used to be.

Split where ownership already diverges. Don’t split to create ownership that doesn’t exist yet.

Let the pain tell you when

You don’t need to go from monolith to a full microservices architecture. You can run the same monolith as a dedicated instance scoped to one workload, same codebase, different entry point. Or extract one service when the monolith is already behaving like two systems. This is the strangler fig pattern, you gradually replace parts of the monolith as they earn their independence, not all at once.

Microservices start earning their cost when:

  • Teams need to deploy independently without coordinating releases
  • Domains have stabilised and boundaries are unlikely to move
  • Scaling profiles genuinely diverge (search vs transactions vs media processing)
  • Ownership is clear, one team per service, one service per team
  • Failure isolation becomes valuable, one component going down shouldn’t take the system with it

Before these conditions exist, microservices mostly add ceremony. After they exist, the split feels natural because the monolith is already showing you where the seams are.

Every split moves you up the coordination ladder

Every change has a coordination cost. Microservices move changes up this ladder earlier than most teams expect:

  • Cheap to change alone -> Refactor (inside a module)
  • Cheap to change as a team -> Deploy (inside a service)
  • Requires coordination -> Across services
  • Requires negotiation -> Across teams
  • Requires planning cycles -> Across the organisation

In a monolith, most changes stay at the top: refactor, deploy, done. In a microservices architecture, changes that would have been a refactor become a coordination exercise. A field rename that touches two modules is an afternoon. The same field rename across two services with a shared contract is a migration plan, a compatibility window, and a rollout sequence.

The question isn’t whether microservices are good or bad. It’s whether your team is ready for the coordination cost they introduce. Introduce service boundaries when teams need independence, not when architecture diagrams do.