---
# === IDENTITY ===
id: software/migrations/monolith-to-microservices/2026
canonical_question: "How do I decompose a monolith into microservices?"
aliases:
  - "monolith to microservices migration guide"
  - "break up monolith into microservices"
  - "strangler fig pattern microservices"
  - "decompose monolithic application"
  - "monolith decomposition strategies"
  - "migrate from monolith to microservices step by step"
  - "microservices extraction from monolith"
  - "modular monolith to microservices"
entity_type: software_reference
domain: software > migrations > monolith_to_microservices
region: global
jurisdiction: global
temporal_scope: 2020-2026

# === VERIFICATION ===
last_verified: 2026-05-17
confidence: 0.93
freshness: evolving
version: 2.1
first_published: 2026-02-17

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: evolving
  last_breaking_change: null
  next_review: 2026-11-13
  change_sensitivity: medium

# === CONSTRAINTS ===
constraints:
  - "Never attempt a big-bang rewrite — use Strangler Fig or Branch by Abstraction for incremental extraction"
  - "Each microservice must own its own database — shared databases create distributed monoliths that are worse than the original"
  - "Do not decompose before mapping bounded contexts with DDD — wrong service boundaries are expensive to fix and require re-merging services"
  - "Ensure operational maturity (CI/CD, monitoring, tracing, alerting) before extracting more than 3 services — distributed debugging without observability is nearly impossible"
  - "Budget 40-60% of total migration effort for database decomposition and eventual consistency patterns — this is the hardest step, not the code split"
  - "Teams smaller than 10 developers should consider a modular monolith instead — operational overhead of microservices typically exceeds the benefits at small scale"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "Application is a simple CRUD app with a small team (<10 devs) and no scaling bottlenecks"
    use_instead: "Consider a modular monolith architecture instead of microservices"
  - condition: "User wants to migrate between cloud providers or containerize an existing app without decomposing"
    use_instead: "software/migrations/docker-compose-to-kubernetes/2026"
  - condition: "User needs microservices communication patterns but has already decomposed"
    use_instead: "software/system-design/microservices-communication/2026"
  - condition: "User has decomposed services and needs distributed transaction patterns"
    use_instead: "software/system-design/cqrs-event-sourcing/2026"

# === AGENT HINTS ===
inputs_needed:
  - key: team_size
    question: "How many developers work on this codebase?"
    type: choice
    options: ["Small (<10)", "Medium (10-50)", "Large (50-100)", "Very large (>100)"]
  - key: monolith_size
    question: "How large is the monolith codebase?"
    type: choice
    options: ["Small (<50K LOC)", "Medium (50K-250K LOC)", "Large (250K-1M LOC)", "Very large (>1M LOC)"]
  - key: primary_driver
    question: "What is the primary reason for decomposition?"
    type: choice
    options: ["Independent deployability", "Team autonomy / Conway's Law", "Targeted scaling", "Regulatory isolation (PCI/HIPAA)", "Technology diversity"]

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/software/migrations/monolith-to-microservices/2026"
suggested_citation: "Source: knowledgelib.io — AI Knowledge Library (verified 2026-05-17)"

# === RELATED UNITS ===
related_kos:
  related_to:
    - id: "software/system-design/microservices-communication/2026"
      label: "Microservices Communication Patterns"
    - id: "software/system-design/cqrs-event-sourcing/2026"
      label: "CQRS and Event Sourcing"
    - id: "software/system-design/api-gateway/2026"
      label: "API Gateway Design"
    - id: "software/system-design/message-queue-event-driven/2026"
      label: "Message Queue and Event-Driven Architecture"
  solves:
    - id: "software/migrations/docker-compose-to-kubernetes/2026"
      label: "Docker Compose to Kubernetes Migration"
  often_confused_with:
    - id: "software/system-design/microservices-communication/2026"
      label: "Microservices Communication Patterns — runtime concern, not decomposition strategy"

# === SOURCES (8 authoritative sources) ===
# Types: official_docs, technical_blog, rfc_spec, academic_paper, community_resource, industry_report
# Reliability: high, moderate_high, moderate, moderate_low, low, authoritative
sources:
  - id: src1
    title: "Strangler Fig Application"
    author: Martin Fowler
    url: https://martinfowler.com/bliki/StranglerFigApplication.html
    type: technical_blog
    published: 2019-06-19
    reliability: authoritative
  - id: src2
    title: "Monolith to Microservices: Evolutionary Patterns to Transform Your Monolith"
    author: Sam Newman
    url: https://samnewman.io/books/monolith-to-microservices/
    type: technical_blog
    published: 2024-11-01
    reliability: authoritative
  - id: src3
    title: "Strangler Fig pattern"
    author: Microsoft Azure Architecture Center
    url: https://learn.microsoft.com/en-us/azure/architecture/patterns/strangler-fig
    type: official_docs
    published: 2024-06-01
    reliability: authoritative
  - id: src4
    title: "Monolith Decomposition Patterns"
    author: Sam Newman (NDC Conference)
    url: https://www.youtube.com/watch?v=64w1zbpHGTg
    type: community_resource
    published: 2024-01-15
    reliability: high
  - id: src5
    title: "Microservices Architecture on AWS"
    author: Amazon Web Services
    url: https://docs.aws.amazon.com/whitepapers/latest/microservices-on-aws/microservices-on-aws.html
    type: official_docs
    published: 2024-08-01
    reliability: authoritative
  - id: src6
    title: "From monolith to microservices: how to break a monolith application into microservices"
    author: Dynatrace
    url: https://www.dynatrace.com/news/blog/how-to-break-a-monolith-into-microservices/
    type: technical_blog
    published: 2025-01-15
    reliability: high
  - id: src7
    title: "BoundedContext"
    author: Martin Fowler
    url: https://martinfowler.com/bliki/BoundedContext.html
    type: technical_blog
    published: 2014-01-15
    reliability: authoritative
  - id: src8
    title: "Microservices vs Monoliths in 2026: When Each Architecture Wins"
    author: Java Code Geeks
    url: https://www.javacodegeeks.com/2025/12/microservices-vs-monoliths-in-2026-when-each-architecture-wins.html
    type: industry_report
    published: 2025-12-01
    reliability: high
  - id: src9
    title: "The Great Microservices Consolidation — What the CNCF 2025 Survey Reveals"
    author: SoftwareSeni
    url: https://www.softwareseni.com/the-great-microservices-consolidation-what-the-cncf-2025-survey-reveals-about-industry-trends/
    type: industry_report
    published: 2026-01-20
    reliability: high
---

# How to Decompose a Monolith into Microservices

## TL;DR

- **Bottom line**: Use the Strangler Fig pattern to incrementally extract bounded contexts from your monolith into independent services — never attempt a big-bang rewrite. In 2025-2026, the industry consensus recommends starting with a modular monolith and extracting selectively only when business needs justify it.
- **Key tool/command**: Domain-Driven Design (DDD) bounded context mapping → Strangler Fig facade → database-per-service separation
- **Watch out for**: Creating a "distributed monolith" — splitting code into services that still share a database and must be deployed together defeats the entire purpose. A 2025 CNCF survey found 42% of organizations are consolidating microservices back into larger units due to operational overhead. [src8]
- **Works with**: Any language/framework; commonly implemented with API gateways (Kong, Envoy, AWS ALB), containers (Docker/Kubernetes), service meshes (Istio, Linkerd), and event brokers (Kafka, RabbitMQ).

## Constraints
<!-- Agents: read this section before recommending any action from this unit.
     These are hard constraints that must not be violated. -->

- Never attempt a big-bang rewrite — use Strangler Fig or Branch by Abstraction for incremental extraction. [src1, src2]
- Each microservice must own its own database — shared databases create distributed monoliths that are worse than the original. [src2, src6]
- Do not decompose before mapping bounded contexts with DDD — wrong service boundaries are expensive to fix and require re-merging services. [src7]
- Ensure operational maturity (CI/CD, monitoring, tracing, alerting) before extracting more than 3 services — distributed debugging without observability is nearly impossible. [src5, src6]
- Budget 40-60% of total migration effort for database decomposition and eventual consistency patterns — this is the hardest step, not the code split. [src2]
- Teams smaller than 10 developers should consider a modular monolith instead — microservices infrastructure costs 3.75x-6x more than a monolith for equivalent functionality. [src8]

## Quick Reference

| Decomposition Pattern | When to Use | Risk Level | Effort | Key Requirement |
|---|---|---|---|---|
| Strangler Fig | Incremental migration of any monolith | Low | Medium | API gateway or proxy to route traffic |
| Branch by Abstraction | Extract internal modules without cutting API | Low | Medium | Abstraction layer in monolith codebase |
| Parallel Run | Validate new service produces identical results | Low | High | Dual-write + comparison pipeline |
| Decompose by Business Capability | Clear business functions map to services | Medium | Medium | Business domain expertise |
| Decompose by Subdomain (DDD) | Complex domain with many bounded contexts | Medium | High | Domain modeling workshops |
| Database-per-Service | Eliminate shared DB coupling | High | Very High | Data migration strategy + eventual consistency |
| Change Data Capture (CDC) | Sync data during transition without dual-write | Medium | Medium | CDC tool (Debezium, DynamoDB Streams) |
| Anti-Corruption Layer | Isolate new service from legacy data model | Low | Medium | Translation layer between old/new models |
| Modular Monolith (intermediate) | Not ready for network boundaries yet | Very Low | Low | Clean module boundaries within monolith |
| Saga Pattern | Distributed transactions across services | High | High | Event choreography or orchestrator |

## Decision Tree

```
START
├── Do you have clear, measurable reasons to move to microservices?
│   ├── NO → Stop. A modular monolith may be sufficient. [src2, src8]
│   └── YES ↓
├── How many developers on the codebase?
│   ├── <10 → Strongly consider modular monolith instead. Operational overhead likely exceeds benefit. [src8]
│   └── ≥10 ↓
├── Is this a legacy system with unclear domain boundaries?
│   ├── YES → Run domain modeling / Event Storming workshops first [src7]
│   └── NO ↓
├── Can you place a proxy/gateway in front of the monolith?
│   ├── YES → Use Strangler Fig pattern (recommended default) [src1, src3]
│   └── NO → Use Branch by Abstraction inside the codebase [src2]
├── Does the monolith use a single shared database?
│   ├── YES → Plan database decomposition (hardest part) — start with schema separation [src2, src6]
│   └── NO ↓
├── Are there cross-cutting concerns (auth, logging, config)?
│   ├── YES → Extract these as shared platform services first [src5]
│   └── NO ↓
├── How many concurrent users?
│   ├── <1K → Start with 2-3 services, expand as needed
│   ├── 1K-100K → Containerize with Docker + Kubernetes [src5]
│   └── >100K → Full service mesh + event-driven architecture [src5]
└── DEFAULT → Extract one bounded context at a time, validate, repeat
```

## Step-by-Step Guide

### 1. Define business objectives before touching code

Articulate exactly why you need microservices: independent deployability, team autonomy, targeted scaling, or polyglot tech stacks. If you can't name a specific pain point the monolith causes, you probably don't need microservices yet. As of 2025-2026, cost (not scalability) has become the primary architectural constraint — microservices infrastructure costs 3.75x-6x more than equivalent monoliths. [src2, src8]

```
# Questions to answer before starting:
1. What specific scaling bottleneck does the monolith have?
2. Which teams block each other during deployment?
3. What is the expected deployment frequency per service?
4. What is the acceptable latency budget for inter-service calls?
5. Do you have the operational maturity for distributed systems?
6. What is the infrastructure budget delta (monolith vs microservices)?
```

**Verify**: Document answers. If most answers are vague, invest in a modular monolith first.

### 2. Map bounded contexts with Event Storming

Use Event Storming (or similar domain modeling technique) to identify bounded contexts — areas of the domain with distinct language, rules, and data ownership. Each bounded context is a candidate microservice. AI-assisted tools like vFunction can now automate much of this analysis by recording runtime behavior and visualizing dependency clusters. [src7, src2, src6]

```
# Event Storming output → bounded context candidates
Domain Events:
  [Order Placed] → Order context
  [Payment Processed] → Payment context
  [Item Shipped] → Shipping context
  [Inventory Reserved] → Inventory context

Bounded Contexts identified:
  1. Orders (owns: order data, order lifecycle)
  2. Payments (owns: payment data, refunds)
  3. Shipping (owns: shipment tracking, carrier integration)
  4. Inventory (owns: stock levels, reservations)
  5. Catalog (owns: product data, pricing)
```

**Verify**: Each context has a clear owner (team), distinct data, and minimal cross-context joins in the current database.

### 3. Enforce module boundaries in the monolith first (modular monolith step)

Before extracting any service across a network boundary, refactor the monolith into clean modules with internal APIs. This validates your bounded context boundaries cheaply — if modules can't be separated in-process, they certainly can't be separated over the network. This intermediate step is now widely recommended as of 2025. [src2, src8]

```python
# Example: Enforce module boundaries in a Python monolith
# BEFORE: Tangled imports across domains
from orders.models import Order
from payments.models import Payment  # direct cross-domain import

# AFTER: Each module exposes a public API facade
# payments/api.py (public interface)
def get_payment_status(order_id: str) -> str:
    """Public API — only function other modules may call."""
    payment = Payment.objects.get(order_id=order_id)
    return payment.status

# orders/services.py (uses public API, not internal models)
from payments.api import get_payment_status
status = get_payment_status(order.id)
```

**Verify**: No module imports another module's internal models directly. All cross-module communication goes through public API facades.

### 4. Set up the Strangler Fig facade

Place an API gateway or reverse proxy in front of the monolith. Initially, all traffic routes to the monolith. As you extract services, update routing rules to direct specific paths to the new service. Modern implementations often use Istio or Envoy for traffic splitting with percentage-based rollout. [src1, src3]

```yaml
# Example: nginx routing configuration for Strangler Fig
upstream monolith {
  server monolith-app:8080;
}
upstream orders_service {
  server orders-service:8081;
}

server {
  listen 80;

  # New service handles /api/orders/*
  location /api/orders/ {
    proxy_pass http://orders_service;
  }

  # Everything else still goes to the monolith
  location / {
    proxy_pass http://monolith;
  }
}
```

**Verify**: `curl http://gateway/api/orders/123` returns response from new service; `curl http://gateway/api/products` still hits monolith.

### 5. Extract the first bounded context

Pick the context with the lowest coupling to the rest of the monolith. Extract it into a standalone service with its own repository, CI/CD pipeline, and data store. [src2, src6]

```python
# Example: Extracting a Notifications service from a Django monolith
# BEFORE: Notification logic embedded in monolith
# app/views.py
def place_order(request):
    order = Order.objects.create(...)
    # Tightly coupled: notification logic in order flow
    send_email(order.user.email, "Order confirmed", ...)
    send_sms(order.user.phone, "Order confirmed")
    return JsonResponse({"order_id": order.id})

# AFTER: Monolith publishes event, Notification service consumes it
# monolith/views.py
def place_order(request):
    order = Order.objects.create(...)
    # Publish event instead of direct call
    publish_event("order.placed", {
        "order_id": order.id,
        "user_email": order.user.email,
        "user_phone": order.user.phone
    })
    return JsonResponse({"order_id": order.id})

# notification-service/consumer.py (separate service)
def handle_order_placed(event):
    send_email(event["user_email"], "Order confirmed", ...)
    send_sms(event["user_phone"], "Order confirmed")
```

**Verify**: Place an order via the monolith -> event appears in message broker -> notification service sends email/SMS. Monolith no longer imports notification code.

### 6. Decompose the shared database

This is typically the hardest step. Start by separating schemas, then migrate to physically separate databases. Use Change Data Capture (CDC) or dual-write during transition. With 83% of data migrations failing or exceeding budgets, plan conservatively. [src2, src6]

```sql
-- Step 1: Schema separation within the same database
-- Move order tables to their own schema
CREATE SCHEMA orders_service;
ALTER TABLE orders SET SCHEMA orders_service;
ALTER TABLE order_items SET SCHEMA orders_service;

-- Step 2: Replace cross-schema joins with API calls
-- BEFORE: Direct join in monolith
-- SELECT o.*, p.name FROM orders o JOIN products p ON o.product_id = p.id;

-- AFTER: Orders service calls Catalog API
-- GET /api/catalog/products/123 → {"id": 123, "name": "Widget"}
```

```bash
# Step 3: Use Debezium CDC to sync data during transition
# Debezium captures changes from the monolith DB and streams to Kafka
docker run -d --name debezium \
  -e BOOTSTRAP_SERVERS=kafka:9092 \
  -e GROUP_ID=1 \
  -e CONFIG_STORAGE_TOPIC=debezium-configs \
  debezium/connect:2.5

# Register a connector for the orders table
curl -X POST http://localhost:8083/connectors -H "Content-Type: application/json" -d '{
  "name": "orders-connector",
  "config": {
    "connector.class": "io.debezium.connector.postgresql.PostgresConnector",
    "database.hostname": "monolith-db",
    "database.dbname": "app",
    "table.include.list": "public.orders,public.order_items",
    "topic.prefix": "orders"
  }
}'
```

**Verify**: Changes to the orders table in the monolith DB appear as events in the `orders.public.orders` Kafka topic within seconds.

### 7. Implement observability before scaling

Before extracting more services, ensure you have centralized logging, distributed tracing, and health monitoring. Without these, debugging distributed systems becomes impossible. OpenTelemetry has become the industry standard for instrumentation as of 2025. [src5, src6]

```yaml
# docker-compose.yml — minimal observability stack
services:
  jaeger:
    image: jaegertracing/all-in-one:1.54
    ports:
      - "16686:16686"  # UI
      - "4318:4318"    # OTLP HTTP

  prometheus:
    image: prom/prometheus:v2.50.0
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    ports:
      - "9090:9090"

  grafana:
    image: grafana/grafana:10.3.0
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
```

**Verify**: Service traces visible in Jaeger at `http://localhost:16686`. Metrics scraped by Prometheus. Dashboards rendering in Grafana.

### 8. Iterate: extract more bounded contexts

Repeat steps 5-6 for each bounded context, prioritizing by coupling pain and business value. Expect 6-18 months for a medium monolith (100K-500K LOC) to fully decompose. The 2025-2026 industry recommendation is a hybrid: modular monolith core plus 2-5 extracted services for hot paths. [src2, src4, src8]

```
Extraction order (recommended):
1. Notifications (low coupling, high blast radius reduction)
2. Authentication/Authorization (cross-cutting, stabilize early)
3. Payments (critical path, isolate for PCI compliance)
4. Inventory (high-contention, benefits from independent scaling)
5. Orders (core domain, extract last — highest coupling)
6. Catalog (read-heavy, easy to cache independently)
```

**Verify**: Each extraction reduces the monolith's LOC by 10-20%. Deployment frequency per service should increase measurably.

## Code Examples

### Python/FastAPI: Strangler Fig with API Gateway routing

> Full script: [python-fastapi-strangler-fig-with-api-gateway-rout.py](scripts/python-fastapi-strangler-fig-with-api-gateway-rout.py) (35 lines)

```python
# Input:  A monolith where /api/orders is being extracted to a new service
# Output: A FastAPI gateway that routes between monolith and new services
from fastapi import FastAPI, Request
import httpx
app = FastAPI()
# ... (see full script)
```

### TypeScript/NestJS: Event-driven service extraction

> Full script: [typescript-nestjs-event-driven-service-extraction.ts](scripts/typescript-nestjs-event-driven-service-extraction.ts) (52 lines)

```typescript
// Input:  A bounded context extracted as a NestJS microservice
// Output: Service that consumes domain events from the monolith via RabbitMQ
import { NestFactory } from '@nestjs/core';
import { MicroserviceOptions, Transport } from '@nestjs/microservices';
import { Module, Controller } from '@nestjs/common';
# ... (see full script)
```

### Go: Anti-Corruption Layer between monolith and new service

> Full script: [go-anti-corruption-layer-between-monolith-and-new-.go](scripts/go-anti-corruption-layer-between-monolith-and-new-.go) (67 lines)

```go
// Input:  Legacy monolith uses a different data model than the new service
// Output: ACL translates between legacy and new domain models
package acl
import (
	"context"
# ... (see full script)
```

## Anti-Patterns

### Wrong: Big-bang rewrite

```
// BAD — Rewriting everything from scratch before deploying anything
// "We'll freeze features for 6 months while we rebuild in microservices"
// Result: project runs over schedule, business falls behind competitors,
// new system has different bugs than the old one, migration day is chaos.
```

### Correct: Incremental Strangler Fig migration

```
// GOOD — Extract one service at a time, deploy to production, validate, repeat
// Week 1-4: Extract Notifications service → deploy → monitor
// Week 5-8: Extract Auth service → deploy → monitor
// Week 9-12: Extract Payments service → deploy → monitor
// Monolith shrinks gradually. Zero "migration day." [src1, src2]
```

### Wrong: Sharing a database across microservices

```sql
-- BAD — Multiple services reading/writing the same tables
-- orders-service queries:
SELECT * FROM orders WHERE user_id = 123;
-- payments-service queries:
UPDATE orders SET payment_status = 'paid' WHERE id = 456;
-- inventory-service queries:
SELECT * FROM orders o JOIN inventory i ON o.product_id = i.product_id;
-- All services coupled through shared schema. Can't evolve independently.
```

### Correct: Database-per-service with API calls

```python
# GOOD — Each service owns its data; communicates via APIs or events
async def process_payment(payment_data):
    result = await charge_card(payment_data)
    if result.success:
        # Publish event instead of writing to orders DB
        await publish_event("payment.completed", {
            "order_id": payment_data["order_id"],
            "amount": result.amount,
            "transaction_id": result.transaction_id
        })
    return result
# [src2, src6]
```

### Wrong: Extracting services by technical layer

```
// BAD — Splitting by technical concern instead of business domain
//   "frontend-service" — all UI code
//   "api-service" — all API endpoints
//   "database-service" — all database operations
// These are still tightly coupled — changing one feature touches all three.
```

### Correct: Extract services by business capability

```
// GOOD — Each service owns a full vertical slice of functionality
//   "orders-service" — Orders API + Orders DB + Order business logic
//   "payments-service" — Payments API + Payments DB + Payment logic
//   "catalog-service" — Catalog API + Catalog DB + Product logic
// A change to order logic only touches the orders-service. [src2, src7]
```

### Wrong: Making services too small ("nanoservices")

```
// BAD — One service per CRUD operation
//   "create-order-service"
//   "get-order-service"
//   "update-order-status-service"
//   "delete-order-service"
// Operational overhead exceeds benefit. Deployment complexity explodes.
```

### Correct: Service boundaries match bounded contexts

```
// GOOD — Service encompasses a complete bounded context
//   "orders-service" handles: create, read, update, delete, status transitions,
//   order history, and order-related events.
//   One repo, one CI/CD pipeline, one team owns it. [src2, src4]
```

### Wrong: Microservices for a small team (2025-2026 anti-pattern)

```
// BAD — 5-person startup adopting microservices "because Netflix does it"
// Infrastructure cost: $40K-65K/month vs $15K/month for equivalent monolith
// Result: 70% of engineering time spent on infrastructure, not features.
// Amazon Prime Video team moved BACK to monolith, cut costs by 90%. [src8]
```

### Correct: Start modular, extract when justified

```
// GOOD — Build a modular monolith first, extract hot paths as needed
// Phase 1: Modular monolith with clean internal boundaries
// Phase 2: Extract 2-5 services for independently-scaling hot paths
// Phase 3: Continue extracting only when business value justifies overhead
// The 2025-2026 industry consensus for teams with 10-100 engineers. [src8]
```

## Common Pitfalls

- **Creating a distributed monolith**: Services share a database, deploy together, and fail together. If you can't deploy one service without deploying others, you haven't decomposed — you've just added network latency. Fix: enforce database-per-service and independent CI/CD pipelines from day one. [src2]
- **Ignoring data consistency challenges**: Removing cross-service joins breaks queries that assumed ACID transactions. Fix: accept eventual consistency, implement the Saga pattern for distributed transactions, or keep tightly-coupled data in the same service. [src2, src4]
- **Decomposing before understanding domain boundaries**: Extracting services without domain modeling leads to wrong boundaries that are expensive to fix. Fix: run Event Storming workshops with domain experts before writing any new service code. [src7]
- **Underestimating operational complexity**: Each new service needs monitoring, logging, alerting, CI/CD, secret management, and on-call rotation. Fix: build a platform team and shared infrastructure (service mesh, observability stack) before scaling past 5 services. [src5, src6]
- **Migrating for technical purity instead of business value**: Teams decompose the "easiest" modules instead of the ones causing the most business pain. Fix: prioritize extraction by business impact — extract the service that unblocks the most feature development. [src2]
- **Forgetting the Anti-Corruption Layer**: New services directly consume the monolith's data model, inheriting its design flaws. Fix: always put a translation layer between legacy and new domain models. [src3]
- **Skipping the modular monolith step**: Going straight from a tangled monolith to microservices. Fix: first enforce module boundaries within the monolith (separate packages, internal APIs), then extract modules that prove stable as services. [src2, src8]
- **Underestimating cost**: Microservices infrastructure costs 3.75x-6x more than equivalent monoliths. Fix: model total cost of ownership (infrastructure + operations + platform team + coordination overhead) before committing to decomposition. [src8]

## Diagnostic Commands

```bash
# Analyze coupling in a monolith codebase (Java — jdeps)
jdeps --summary --multi-release 21 target/*.jar

# Analyze Python import dependencies (pydeps)
pydeps myapp --max-bacon 2 --cluster

# Count cross-module database joins (PostgreSQL)
grep -rn 'JOIN' --include='*.py' --include='*.java' --include='*.ts' | \
  grep -v 'LEFT OUTER' | wc -l

# Check for shared database tables across services
psql -c "SELECT schemaname, tablename, tableowner FROM pg_tables WHERE schemaname NOT IN ('pg_catalog', 'information_schema');"

# Verify service independence — can each service start alone?
docker compose up orders-service --no-deps
curl http://localhost:8081/health

# Measure inter-service latency
curl -w "time_total: %{time_total}s\n" -o /dev/null -s http://orders-service/api/orders/123

# Check for circular dependencies between services
# (review service dependency graph in your observability tool)

# Verify database isolation — no cross-service schema access
psql -c "SELECT grantee, table_schema, table_name, privilege_type FROM information_schema.table_privileges WHERE grantee NOT IN ('postgres', 'PUBLIC') ORDER BY grantee, table_schema;"
```

## Version History & Compatibility

| Approach | Status | Key Development | Notes |
|---|---|---|---|
| Strangler Fig (Fowler, 2004) | Industry standard | Pattern matured with cloud-native adoption | Still the recommended starting point [src1] |
| DDD Bounded Contexts (Evans, 2003) | Industry standard | Refined by Newman (2019) for microservices | Required for correct service boundaries [src7] |
| Saga Pattern (Garcia-Molina, 1987) | Widely adopted | Orchestration vs. choreography variants | Use for distributed transactions [src2] |
| Modular Monolith (2020s trend) | Growing rapidly (2025) | Recommended intermediate step by 42% of orgs | Consider before microservices [src2, src8] |
| Event-Driven Decomposition | Current best practice | Kafka/RabbitMQ as decoupling mechanism | Enables async communication [src5] |
| AI-Assisted Migration (2025+) | Emerging | vFunction, automated dependency analysis | Saves hours of manual assessment [src6] |
| Hybrid Architecture (2025-2026) | Industry consensus | Modular monolith core + extracted hot paths | Best of both worlds for mid-size teams [src8] |

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| Teams block each other during deployments | Small team (<10 developers) | Modular monolith |
| Specific components need independent scaling | Simple CRUD application | Well-structured monolith |
| Different components need different tech stacks | Unclear domain boundaries | DDD modeling + modular monolith first |
| Regulatory compliance requires isolation (PCI, HIPAA) | Startup still finding product-market fit | Monolith (iterate faster) |
| >100 developers working on same codebase | Budget doesn't cover operational overhead (3.75x-6x) | Vertical slice architecture |
| Deployment frequency needs vary per component | All components have similar load patterns | Monolith with horizontal scaling |

## Decision Logic

Use these if/then rules to route agent recommendations to the correct path. Each rule cites the underlying sources.

### If team size is under 10 developers and no scaling bottleneck is documented
--> Stay on a modular monolith. Microservices infrastructure costs 3.75x–6x more for equivalent functionality and 42% of orgs that adopted microservices are consolidating back. [src8, src9]

### If the monolith uses a single shared database and the goal is service independence
--> Plan database decomposition first (40–60% of total migration effort). Start with schema separation, then add CDC (Debezium) for dual-read during cutover. Do NOT split code while keeping a shared DB — that produces a distributed monolith. [src2, src6]

### If domain boundaries are unclear or the codebase is legacy with tangled imports
--> Run Event Storming workshops with domain experts BEFORE extracting any service. Optionally use AI-assisted analysis tools (vFunction, LLM-based dependency clustering) to accelerate runtime-behavior mapping, but treat their suggestions as drafts that need human review. [src7, src6]

### If an API gateway or reverse proxy can be placed in front of the monolith
--> Use the Strangler Fig pattern with traffic routing rules. Start by routing one bounded context (typically Notifications or Auth) to a new service while everything else still hits the monolith. [src1, src3]

### If a proxy/gateway cannot be inserted at the perimeter
--> Use Branch by Abstraction inside the codebase: introduce an abstraction layer, route internal calls through it, then swap the implementation for an out-of-process service one path at a time. [src2]

### If observability (centralized logs, distributed traces, metrics) is not in place
--> Stop. Install an OpenTelemetry + Jaeger/Prometheus/Grafana stack BEFORE extracting more than 3 services. Debugging distributed systems without traces is effectively impossible. [src5, src6]

### If the team is decomposing primarily to "modernize" with no measurable pain point
--> Reject the migration. Document the specific scaling, deployment, or team-coordination bottleneck first. Recent industry data shows consolidation back to monoliths delivers 87% AWS cost reduction and 93% latency reduction when the original microservices split was not justified. [src9]

## Important Caveats

- Microservices trade code complexity for operational complexity — you need mature CI/CD, monitoring, and incident response before the switch pays off.
- Database decomposition is the single hardest step. Budget 40-60% of total migration effort for data separation and eventual consistency patterns.
- Conway's Law applies: your service boundaries will mirror your team structure. Reorganize teams around bounded contexts, not technical layers.
- Network calls are 100-1000x slower than in-process calls. Every service boundary adds latency. Over-decomposition causes death by a thousand network hops.
- There is no "right number" of microservices. Some organizations thrive with 5 services, others need 500. Let business boundaries and team structure dictate the number.
- As of 2025-2026, the industry is experiencing a significant correction: 42% of organizations that adopted microservices are consolidating back into larger units (CNCF Annual Survey, n=689, ±3.8%). One 2026 case study reported an 87% AWS cost reduction and 93% latency improvement after consolidating back to a monolith. Evaluate whether you truly need microservices before committing. [src8, src9]
- Infrastructure cost is now the primary constraint for many organizations: microservices cost $40K-65K/month vs $15K/month for equivalent monolith functionality. [src8]

## Related Units

- [Microservices Communication Patterns](/software/system-design/microservices-communication/2026)
- [CQRS and Event Sourcing](/software/system-design/cqrs-event-sourcing/2026)
- [API Gateway Design](/software/system-design/api-gateway/2026)
- [Message Queue and Event-Driven Architecture](/software/system-design/message-queue-event-driven/2026)
- [Docker Compose to Kubernetes Migration](/software/migrations/docker-compose-to-kubernetes/2026)
