A monolith deploys as one unit. Microservices split an application into small services that each own a business capability and deploy on their own. The pattern solves real problems at scale, and it introduces new ones. Many teams that migrated too early ended up with a distributed monolith, which keeps the coupling and adds network failures.
Reasons to split
- Several teams block each other because every release touches the same code.
- One part of the system needs to scale far beyond the rest, such as search or video processing.
- Different parts need different technology or different reliability levels.
- A failure in a minor feature takes down critical ones.
- Build and test times have grown to the point that they slow delivery.
Reasons to wait
A team of five with one product rarely benefits. A well-structured modular monolith, with clear module boundaries and enforced dependencies, gives you most of the clarity at a small share of the operating cost. Fix slow queries and messy modules before adding network calls between them.
Prepare the monolith first
- Draw the domain boundaries. Domain-driven design's "bounded contexts" help here: orders, billing, catalog, users.
- Reorganize code into modules that mirror those boundaries and block cross-module imports with lint rules.
- Add automated tests around the area you plan to extract.
- Add logging, metrics and tracing so you can see behavior before and after.
Use the strangler fig pattern
Do not rewrite everything. Place a routing layer, an API gateway or reverse proxy, in front of the monolith. Build one new service, send the matching traffic to it, and leave the rest with the monolith. Repeat. Over time, the old code shrinks until you can retire it. Each step ships to production, so risk stays small and you can roll back a single route.
Choose the first service
Pick a capability with loose coupling, has a clear interface and brings visible value, such as notifications, document generation or search. Avoid starting with the core domain model that every other module touches.
Own your data
The hardest part is the database. Each service should own its data and expose it through an API or events. Shared tables create hidden coupling and make independent deployment impossible. Plan the data split :
- Copy or move tables for the new service in stages, and keep both in sync with change data capture or dual writes during the transition.
- Replace SQL joins across services with API calls or locally stored read models.
- Accept eventual consistency where the business allows it.
- Use the saga pattern for workflows that span services, with compensating actions when a step fails.
Communication
Use synchronous calls (REST, gRPC) when the caller needs an answer right away. Use asynchronous messaging (Kafka, RabbitMQ, SQS, or a cloud pub/sub) for events such as "order placed". Set timeouts, retries with backoff and circuit breakers on every remote call. Design handlers to be idempotent, because messages can arrive twice.
The operational bill
Each service needs its own pipeline, monitoring, alerts, secrets and on-call coverage. Budget for these capabilities before you split:
- Automated CI/CD per service
- Container orchestration or a managed platform
- Centralized logs, metrics and distributed tracing
- Service discovery, an API gateway and consistent authentication
- Contract tests so teams change APIs without breaking each other
Testing
Unit tests stay local. Add consumer-driven contract tests with a tool like Pact, and keep end-to-end tests for a few critical journeys, because they run slowly and break easily across many services.
Measure success
Define targets before you start: deployment frequency, lead time, change failure rate and infrastructure cost. If deployments are not more frequent and incidents are not fewer after a few extractions, revisit the approach.
A realistic timeline
Common questions
How many services is too many?
There is no fixed number, but a useful rule says one team should own a handful of services at most. If a single feature change keeps requiring edits and releases across four services, the boundaries are wrong. Merge them or move the shared logic.
Which language should each service use?
Resist choosing a new language for each service. A small set of approved languages keeps hiring, tooling and on-call knowledge manageable. Use a different language only when a service has a clear technical need, such as Python for machine learning.
How do we keep API contracts stable?
Describe APIs with OpenAPI or protobuf definitions, keep them in version control and test compatibility in CI. Make additive changes, support the old and new fields side by side, and set a retirement date for old versions with notice to consumers.
Expect a first service to take weeks, and a full migration of a mature system to take a year or more. Keep delivering features during the process, and review each step with the whole team. Our architects assess monoliths and plan staged migrations. Request an architecture assessment or read about our engineering services.

