“Should we use microservices?” is one of the most common architecture questions, and the honest answer for most teams is not yet. Microservices solve real problems, but those problems are mostly about large organizations, and they bring a lot of operational complexity. Here’s how the options compare, and how to decide.
The options
| Architecture | What it is |
|---|---|
| Monolith | One application, one codebase, deployed as one unit, usually one database |
| Modular monolith | One deployable, internally split into well-separated modules with clear interfaces |
| Microservices | Many small services, each deployed independently, each owning its data, talking over the network |
Comparison
| Monolith | Microservices | |
|---|---|---|
| Development start | Fast | Slow (infrastructure first) |
| Deployment | One artifact | Many pipelines, versioning between services |
| Local development | Run one app | Run many services (or mocks) |
| Calls between parts | In-process function calls | Network calls: latency, retries, failures |
| Data consistency | Database transactions | Eventual consistency, sagas, events |
| Scaling | Scale the whole app | Scale each service independently |
| Fault isolation | One bug can take everything down | Failures can be contained (if designed well) |
| Team independence | Coordination needed | Teams own and release services independently |
| Operations | One app to monitor | Service discovery, tracing, many dashboards |
| Hosting cost | Low | Higher (more processes, more infrastructure) |
Why monoliths are a good default
- Simplicity: one codebase to understand, one deployment, one database transaction.
- Performance: function calls are far faster than network calls.
- Easy refactoring: moving code between modules is an IDE operation, not an API migration.
- Cheap hosting: a single VPS (plus a database) can serve a lot of traffic with caching. See web application architecture.
Many large, successful products run as (modular) monoliths. Horizontal scaling still works: run several copies of the monolith behind a load balancer. See Nginx load balancing.
When microservices make sense
- Many teams (dozens of engineers) who block each other’s releases in one codebase.
- Parts with very different scaling needs (a video transcoding service vs a small admin panel).
- Parts needing different technologies (a Python ML service next to a Go API).
- Fault isolation requirements: a failing recommendation engine mustn’t break checkout.
- You already have mature CI/CD, monitoring, tracing and on-call practices.
If none of these apply, microservices mostly add cost.
The middle path: a modular monolith
Structure the monolith into modules with clear boundaries (orders, billing, users, catalog), each with its own internal API and its own tables, and rules against reaching into another module’s internals. You keep the simplicity of one deployment, and if a module ever needs to become a service, the boundary already exists.
Extracting a service step by step
When a real reason appears:
- Pick one module with a clear boundary and a reason to separate (scaling, team, technology).
- Put an API in front of it inside the monolith first.
- Give it its own data store, and migrate data access through the API.
- Deploy it separately, and route calls over the network (HTTP/gRPC, or a queue for async work).
- Add monitoring, tracing and error tracking for the new service. See OpenTelemetry and error tracking with Sentry.
This is the “strangler fig” approach: grow new services around the old system, without a risky big rewrite.
What microservices require
| Need | Typical tools |
|---|---|
| Containers and orchestration | Docker, Kubernetes or k3s, Nomad. See k3s |
| Service-to-service communication | HTTP/gRPC, message queues. See background jobs and queues |
| Configuration and secrets | Environment variables, Vault, Kubernetes secrets |
| Observability | Centralized logs, metrics, distributed tracing |
| Deployment | CI/CD per service, versioned APIs |
| Resilience | Timeouts, retries, circuit breakers |
Hosting on VPSs
- Monolith: one VPS (or a few behind a load balancer), plus a database server. Simple and cost-efficient.
- A few services: Docker Compose on one or two VPSs works well. See Kubernetes vs Docker Compose.
- Many services: a small Kubernetes cluster (k3s) across several VPSs, with a private network between them.
Decision guide
| Situation | Choose |
|---|---|
| New product, small team (< 10 devs) | Monolith, structured as modules |
| Growing team, release conflicts | Modular monolith, extract hotspots |
| Many teams, mature DevOps, distinct scaling needs | Microservices |
| Unsure | Monolith: splitting later is easier than merging |
Related: Docker Compose in production and deployment strategies.