For a new business application built by one development team, a single, well-structured application is usually a sensible place to start. Microservices solve specific problems, such as letting several teams release their work independently or scaling one heavy workload separately from the rest, and they bring real costs in testing, monitoring, data consistency and hosting. The decision should come from the problems your application actually has, or clearly will have.
This guide explains the options in plain terms for business owners and SaaS founders who are commissioning an application or weighing up a developer's proposal to change one.
What the terms mean
A monolith is an application whose core business logic is packaged, tested and released as one unit. It can still use background workers and outside services for things like email or payments. What makes it a monolith is that the main business rules, such as placing an order and updating stock, are released together as one application.
Whether the frontend and backend are deployed together does not decide it either way. We have built custom frontends on top of WooCommerce backends when the client wanted features that were easier to build that way. The frontend and backend then run separately, but a separate frontend does not, by itself, make the system microservices.
A modular monolith is still one deployable application, but its code is divided into clearly separated areas, such as billing, bookings and reporting, with rules about how they can talk to each other. In 2019, Shopify described evolving its core codebase into a modular monolith, which it defined as a system where all the code powers a single application and there are "strictly enforced boundaries between different domains" (Shopify Engineering).
Microservices split the application into separate services that are deployed independently and communicate over the network. Microsoft's architecture guidance describes them as small, independent, loosely coupled components that a single small team can write and maintain, with each service owning its own data (Microsoft Azure Architecture Center). That last point matters. Services that read and write each other's tables directly, share responsibility for the same data or have to be released together carry the costs of separate services without the independence that justifies them. Practitioners often call this a "distributed monolith".
Why a single application is often a sensible starting point
A single application is simpler to build, test, deploy and debug. A change that touches orders and invoices can be made, tested and released in one step. When something goes wrong, the whole sequence of events is in one place. Hosting is simpler too.
Experienced practitioners disagree on the starting point. Martin Fowler's essay Monolith First observes that almost all the successful microservice stories he had heard began as monoliths that were later split, though he calls his own advice "tentative". Stefan Tilkov's reply, Don't start with a monolith, argues that monoliths tend to become so tangled that splitting them later is very hard. Both are practitioner views, and they point to the same practical lesson: internal structure matters more than the label. Clear boundaries between the parts of a single application make a later split far more manageable, while an application where everything depends on everything is hard to split and is not fixed by turning it into services.
A single application is not always the right start. Separate services can make sense from the beginning when different teams will own clearly separate parts of the product, when one component has very different technical needs (such as heavy media processing), or when part of the system must be isolated for security or compliance reasons.
At retroXpect, the architecture follows the scope. We scope each project with the client based on what the business needs, the client confirms that scope, and the structure of the application is chosen to fit it.
Traffic, data and team growth
Three common sources of scaling pressure are more traffic, more data and a bigger development team. Each creates different constraints.
Traffic growth means more people using the application at the same time. A single application can often handle this by running several copies behind a load balancer, provided it is built to allow that. The limit can end up being the database or a specific slow operation, and splitting the application into services does not remove either of those. Our guide to web application performance covers how to find what is actually slow.
Data growth means more records, larger tables and heavier reports. A search that was instant with 10,000 records can crawl with 10 million. The fixes are usually in how data is stored and queried: better indexes, archiving old records, moving heavy reports to background jobs or a separate reporting database. Microservices do not make a slow query fast.
Team growth means more developers working on the same codebase. With a handful of developers, one codebase is manageable. As the number grows, people start changing the same files, waiting for each other's work to be tested and queuing for releases. This is the problem microservices are best suited to, because each team can own and release its service independently. Fowler notes that separate services reinforce module boundaries, which "is particularly important for larger teams" (Microservice Trade-Offs). A modular monolith with enforced boundaries is a step that addresses much of the same problem without the network in between.
For many business applications and early SaaS products, the team is small enough that the third problem has not arrived yet.
What separate services cost
Each service is simpler on its own, but the system as a whole becomes more complex. Microsoft's guidance puts it in almost exactly those words. The costs show up in several places.
Network failures and delays. Calls between parts of a monolith happen in memory. Calls between services cross the network, where they are slower and can fail. Fowler describes remote calls as "slow and always at risk of failure". Every service that calls another needs to handle timeouts, retries and the other service being down. Our guide to API integration explains what that involves.
Failure isolation is not automatic. Microsoft notes that a failed service does not disrupt the whole application only "as long as any upstream microservices are designed to handle faults correctly". If the checkout service waits indefinitely for the inventory service, an inventory outage still stops checkout.
Deployment and versioning. Services are released independently, which is the main benefit, but a change to one service must not break the services that depend on it. That means versioning the communication between services and coordinating changes that cross boundaries.
Testing. Testing an individual service can have a narrower scope, but testing its dependencies and how services behave together, especially while they are all changing, is harder and usually needs dedicated test environments and automated checks.
Monitoring and troubleshooting. When a customer reports a problem, the cause might be in any of several services. Teams need centralised logging and tracing that follows a request across services. Fowler lists basic monitoring, quick server provisioning and fast automated deployment as prerequisites to have in place before adopting microservices.
Data consistency. In a single database, saving an order and reducing stock can happen in one transaction that either fully succeeds or fully fails. When each service owns its data, each service still has its own local transactions, but a single database transaction no longer automatically covers the whole workflow. Microsoft's guidance explains that a change spanning several services is unlikely to be one all-or-nothing transaction, so the system has to tolerate data being briefly out of step. Patterns such as the saga pattern handle this with a sequence of local steps and "compensating" actions, such as refunding a payment if a later step fails. They work, but they add design, testing and debugging effort, and a compensating action can itself fail.
Operating costs and skills. More services usually mean more infrastructure to run, monitor and pay for, and a team comfortable operating distributed systems. Costs depend heavily on the workload. One widely discussed example is Amazon Prime Video's video quality monitoring tool, which its team moved from separately orchestrated components into a single application. Amazon's CTO, Werner Vogels, wrote that the team's "experience and analysis" led them to that design, and used it to argue that "there is no one-size-fits-all" (All Things Distributed). It concerns one component with a particular workload, so it shows that separate services are not automatically the better or cheaper choice, without showing that a single application always is.
What microservices will not fix
Some problems that look like architecture problems sit elsewhere.
- Slow queries stay slow when they move into a service. They now also sit behind a network call.
- Poor permissions stay poor. Splitting the application multiplies the places where access has to be checked, which creates more opportunities for mistakes. For SaaS products, keeping each customer's data separate is a design question in its own right, covered in our SaaS development cost guide.
- Tightly coupled business logic does not become loosely coupled because it is deployed separately. If billing, bookings and notifications all depend on each other's internal details, splitting them produces services that must change and release together, connected by a network instead of function calls.
If any of these is the real problem, fixing it inside the current application is usually cheaper, and it makes any later split easier.
Evidence that can justify extracting a service
A good case for a separate service rests on a specific, measured problem. For example:
- A workload with very different resource needs. Video processing, large file conversions or machine learning tasks can need far more memory or processing power than the rest of the application, or need it in bursts. Running them separately lets them scale without scaling everything else.
- A deployment bottleneck. Several teams regularly wait on each other to release, and the delays can be shown in the release history.
- Different reliability or security requirements. One part of the system needs stricter isolation, a different release schedule or a separate security boundary.
- A genuinely different technology need. One component is far better built with a different language or data store.
Before extracting a service, check whether a simpler step solves the problem. Many heavy tasks can move into background jobs within the same application. Enforcing module boundaries inside the codebase can relieve team friction.
An illustrative example. Imagine a booking platform where generating monthly PDF statements for every customer slows the whole application at the start of each month. The first step would be moving statement generation into a background job queue within the same application, so customers no longer wait for it while they make bookings. The workers processing the queue can still compete with bookings for processing power and database capacity, so they would be given limits on how many statements they run at once and suitable resources of their own. If measurements then show the statement work needs far more computing power than the rest of the system, and only for a few days a month, the workers can be scaled separately while still using the same codebase. A fully separate service only becomes worth considering if that is still not enough.
Extracting services gradually
When separating part of an existing system is justified, doing it in stages can reduce migration risk by limiting the size of each change, although Microsoft notes that replacing a small system in one go can be simpler. It documents the staged approach as the Strangler Fig pattern: a routing layer sits in front of the existing application, and requests for one piece of functionality are gradually redirected to the new service until the old code for it can be removed.
Moving code into a separate repository or server is only part of the work. The harder parts are usually about data and transactions:
- Data ownership. Decide which service owns each piece of data. Guidance on Fowler's site recommends keeping a single writable copy of the data throughout the transition, so two systems are never updating the same records independently.
- Removing shared database links. Database joins and foreign keys between the new service's data and the rest of the system have to be redesigned, using API calls, events or local copies of the data each part needs.
- Transaction boundaries. Any process that used to update data in both areas within one transaction needs a new design, such as the saga approach described above.
- Moving callers in order. In the staged approach described in that guide, all parts of the system that read or write the extracted data switch to the new service before its data moves to a separate database.
- Finishing the job. The old code and database tables are removed only when nothing depends on them. Until then, two versions of the same logic exist and both need maintaining.
The same guide recommends completing every step for one service before starting the next. A half-finished extraction leaves the system more complicated than either the starting point or the goal.
Monolith, modular monolith and microservices compared
| Monolith | Modular monolith | Microservices | |
|---|---|---|---|
| Deployment | One application | One application | Many services, released independently |
| Internal structure | Depends on discipline | Enforced boundaries between areas | Separate deployment boundaries; loose coupling still needs careful design |
| Scaling | Scales as one application, though web servers and background workers can sometimes scale separately | Same as a monolith | Individual services can scale separately |
| Data consistency | Transactions are straightforward when the relevant data sits in one database | Same as a monolith | Each service owns its data; workflows across services need extra design |
| Operational overhead | Typically less deployment and monitoring overhead for smaller systems | Similar, plus checks that boundaries are respected | More coordination, testing and observability requirements; total cost depends on the workload |
| Typical fit | Small team, early product | Growing product and team | Several teams, workloads with distinct needs |
Questions to ask a developer proposing microservices
- What specific problem will separate services solve, and how was it measured?
- Could a modular structure or background jobs solve it inside the current application?
- Which service will own each piece of data, and how will processes that span services stay consistent?
- How will failures between services be handled, and what happens to users when one service is down?
- What monitoring, logging and testing will be in place before the first service goes live?
- How will the extra hosting and maintenance costs compare with today?
- Who on the team has run a system like this in production?
- If it is an extraction, what is the order of steps, and when will the old code be removed?
Clear, specific answers suggest the proposal is grounded in the system's real constraints.
Frequently asked questions
Do I need Kubernetes to run microservices?
No. Kubernetes is a platform for running and coordinating many containers, and it is a common choice for large microservices systems, but it brings its own setup and maintenance work. Microservices can also run on managed container services, platform hosting or serverless functions. The right option depends on how many services there are and who will operate them.
Is serverless the same as microservices?
No. Serverless describes how code is hosted: the provider runs it on demand and manages the servers. Microservices describe how an application is divided. A microservices system can run on serverless hosting, and parts of a single application can too. Amazon Prime Video's monitoring tool, mentioned above, was built from serverless components before the team moved it into one application.
Can a system move back from microservices to a single application?
Yes, and the Prime Video example is one case. Moving back takes planning of its own, because data that was split between services has to be brought together and the communication between services replaced. It makes sense when the separate services are adding cost and complexity without delivering the independence they were meant to provide.
Planning or Rethinking Your Application's Architecture?
Whether you are commissioning a new application or reviewing a proposal to split an existing one, the questions above are a good place to start.
retroXpect builds custom web applications structured around the confirmed scope, improves existing systems where structure, speed or maintainability is holding the business back, and maintains applications after launch on plans from S$500 per month, with details in our maintenance cost guide. For the wider decisions involved in a new build, see our guide to custom web application development. Message us on WhatsApp with what your application does and what is prompting the architecture question, and we will suggest a sensible next step.


