At some point in every growing product, someone says the codebase needs to become microservices. Sometimes they are right. Often they have watched a conference talk from a company with five thousand engineers and a very different problem. This guide sets out what each approach really costs, how to recognise the moment to split, and how to do it without betting the business on a rewrite.
What the terms actually mean
A monolith is one application, deployed as one unit, usually with one main database. Your customer app, admin panel and background jobs might all live in the same codebase and ship together. That is not a dirty word. Many large, successful products, including well-known e-commerce and SaaS platforms, still run on carefully organised monoliths today.
Between the two sit other options worth knowing about. Serverless functions let you run small, event-driven pieces, such as image resizing or webhook handling, without managing servers. Background workers and queues let a monolith hand slow jobs to separate processes. Both can relieve pressure on a busy system without committing to a full microservices architecture.
Microservices split the system into small, independent services, each owning its own data and deployed on its own schedule. Orders, payments, inventory and notifications might each be a separate service, talking to the others through APIs or message queues. The promise is that teams can work and release independently, and that each part can scale on its own.
The case for starting with a monolith
For a team of fewer than about fifteen to twenty engineers, a monolith is almost always faster. One repository, one deployment pipeline, one database to back up and one place to look when something breaks. Refactoring across features is a normal code change rather than a coordinated release between several services and several teams.
It is also cheaper to run. A monolith on a couple of servers or containers behind a load balancer, with a managed database, can serve a surprising amount of traffic. For most businesses in the region, from a Dubai retailer to a Riyadh logistics start-up, the first scaling problems are slow queries and missing caches, not the architecture itself.
Where monoliths do go wrong
Monoliths get their bad reputation from neglect, not from the idea itself. When every part of the code can reach into every other part, a change to invoicing breaks the booking calendar, nobody is sure what is safe to touch and releases slow to a crawl. That is a design problem, and splitting into services without fixing it only spreads the mess across a network.
Microservices don’t remove complexity. They move it from your code into your network.
The modular monolith middle ground
The choice is rarely all or nothing. A modular monolith keeps one deployable application but draws firm boundaries inside it. Each module, such as billing, catalogue or bookings, owns its own tables and exposes a small internal interface. Other modules aren’t allowed to reach into its data directly, and those rules are enforced in code review or with tooling.
This gives you most of the organisational benefit of microservices with very little of the operational cost. Teams can own modules, the code stays understandable, and if one module genuinely needs to become its own service later, the boundary is already there. In our experience, this is the right shape for the large majority of growing products we work on.
Enforcing those boundaries is mostly discipline, helped by tooling. Separate database schemas per module, internal packages with explicit public interfaces, and automated architecture tests that fail the build when one module imports another’s internals all keep the structure honest. Without them, boundaries erode within months, especially when deadlines are tight.
When microservices earn their keep
Splitting makes sense when a specific, measurable pain can only be solved by independence. Notice that team size and organisation appear as often as raw performance in this list. These are the signals we look for before recommending it:
- Teams block each other — several teams queue to release, and one team’s bug regularly rolls back another team’s work.
- Uneven load — one part, such as search or image processing, needs ten times the resources of everything else.
- Different reliability needs — payments must stay up even if the recommendations engine falls over.
- Different technology — a machine learning component that is far better written in Python than the rest of your stack.
- Regulatory isolation — card data under PCI DSS, or personal data that must stay in a specific region under UAE or Saudi rules.
If none of those apply, the honest answer is usually to improve the monolith: add caching, tidy the database, break up the worst modules and invest in automated tests. Before committing, it also helps to see what you are signing up for on the operational side, which is where most teams underestimate the effort.
Beware the distributed monolith
The worst outcome is a system split into services that still depend tightly on each other. Every release needs several services to deploy together, a single slow service brings the rest down and a simple feature touches five repositories. You get all the operational cost of microservices with none of the independence, and it is far harder to undo than a messy monolith.
The people cost is easy to miss. Running many services well usually means someone dedicated to the platform: build pipelines, container orchestration, monitoring and security. It also means a proper on-call rota, because failures now happen between services as well as inside them. For a team of eight, that overhead can swallow a significant share of engineering time that would otherwise go into features.
How to split without a rewrite
If you do need to split, avoid the big-bang rewrite. The safer method is often called the strangler fig pattern: you carve out one piece at a time, route traffic to the new service, and retire the old code only when the new one has proven itself in production. The business keeps running throughout, and you can stop at any point.
Pick the first service carefully
Choose something with clear boundaries and real pain, such as notifications, search or file processing. Avoid starting with the core of the business, like orders or accounts, where every other part depends on it. A successful first extraction teaches your team about deployment, monitoring and data ownership with much less at stake.
Untangle the data deliberately
Code is the easy part; data is the hard part. A service that still reads and writes the monolith’s tables isn’t really independent. Plan how each extracted service will own its data, how other parts will get the information they need through APIs or events, and how you will keep both copies consistent during the transition period.
Build the platform before the services
Before the second or third service, invest in the basics: containerised deployments, centralised logging, distributed tracing, health checks and a clear way for services to discover and authenticate each other. Without this foundation, each new service adds confusion faster than it adds value, and on-call engineers spend nights chasing problems across systems.
Measure before and after
Decide in advance what success looks like. If the goal is faster releases, measure deployment frequency and lead time before and after. If it is reliability, track uptime and incident counts. Clear numbers keep the project honest and tell you whether to extract the next service or stop and consolidate what you have.
The bottom line
Architecture should follow your team and your problems, not fashion. For most growing companies, a clean modular monolith carries them comfortably through their first several years. When real pressure appears, such as teams blocking each other, uneven load or regulatory isolation, extract services gradually. That way you keep shipping while the system evolves, instead of pausing the business for a rewrite.
If you are unsure where you stand, ask three questions. Which specific problem would splitting solve? Could a cheaper change to the monolith solve it instead? And do we have the people and tooling to run more moving parts reliably? If the answers are vague, keep the monolith, tidy its boundaries and revisit the question in six months.
