Monolith to Services
Decompose along bounded-context seams using the strangler fig: incrementally route features to new services while shrinking the monolith. Each service owns its data — no shared database.
The B2B billing platform had 200,000 lines in one Rails monolith. The CTO approved a migration to services. The team chose the straightforward approach: pick the most complex subsystem — order management — and rewrite it as a microservice. Six engineers worked for four months on the new OrderService. On launch day, they ran the new service alongside the monolith and switched traffic. Within an hour, the new service and the monolith were serving contradictory order states. The problem: the migration had moved the order API, but the order data was still in the shared database. Billing still wrote directly to the orders table. Inventory still read the orders table via its ORM. The OrderService had the API, but not ownership of the data. Two months later the project was paused. The lesson: you cannot extract a service by moving the API. You must move the API and the data ownership together, incrementally, along seams that already exist in the domain.
Finding the seams: bounded contexts as decomposition units
The right seam for service extraction is a bounded context (unit 06): a region of the model where one consistent set of concepts and language applies. A bounded context already has a natural API surface — the translations it exports to other contexts — and a natural data boundary — the aggregates it owns.
Extracting along the wrong seam is the most common and most expensive mistake in microservices migrations. Wrong seams produce services that are tightly coupled at the data level even when they are separated at the API level. Signs of a wrong seam:
- The extracted service reads tables it doesn’t own, requiring the monolith to keep those tables alive.
- Two services must coordinate on every write to maintain consistency, effectively requiring a distributed transaction.
- A single business operation requires API calls to three or four services in a fixed sequence — the services are fragments of a workflow, not autonomous capabilities.
Right seams correspond to business capabilities that can be described in one sentence with no “and”: “manages the inventory of products,” “handles the billing lifecycle,” “tracks order status from placement to delivery.” If the description requires “and,” it may be two seams glued together.
▸Why this works
Bounded contexts are the unit of decomposition because they are the unit of model coherence. Within a bounded context, every term has one meaning. The Order in the ordering bounded context is not the same Order in the billing bounded context — one cares about fulfillment state, the other about payment state. Splitting along this boundary means each service has its own model and its own data, with no shared understanding forced by a shared schema.
The strangler fig pattern
The strangler fig (Fowler, 2004) is an incremental migration pattern named after the tropical vine that grows around an existing tree, eventually replacing it. Applied to software:
- Put a proxy in front of the monolith. All traffic passes through the proxy (an API gateway, a routing layer, or a reverse proxy). The monolith handles everything initially.
- Extract one capability into a new service. The new service owns a slice of the domain — the right-sized bounded context.
- Route that capability’s traffic to the new service. The proxy directs requests for the extracted capability to the new service. The monolith’s code for that capability becomes dead.
- Remove the dead code from the monolith. The monolith shrinks. Repeat for the next capability.
At any point, the system is operational. The monolith handles everything the new services don’t yet handle. The migration is continuous and reversible at each step — if the new service fails, the proxy can route traffic back to the monolith’s path.
Data ownership and the database-per-service rule
The most violated rule in microservices migrations is data ownership. The strangler fig can route API traffic to the new service on day one, but migrating data ownership takes longer. Until data ownership transfers, the service is a facade, not an autonomous unit.
Database-per-service means each service has its own persistence store, with no other service reading or writing its tables. This is not primarily a technology choice (one PostgreSQL instance can serve multiple services on different schemas) — it is an ownership contract:
- Only BillingService writes to the
invoicesandchargestables. - No other service holds an ORM model for those tables.
- Other services that need billing data call BillingService’s API or consume its events.
The migration sequence for data ownership:
- Identify all code in the monolith that reads or writes the tables being migrated.
- Wrap those access points behind an interface in the monolith that matches the new service’s API surface.
- Deploy the new service and point the monolith’s interface implementation at the new service’s API.
- Migrate the data to the new schema.
- Remove the monolith’s tables (or schema-lock them so writes are rejected).
A team uses the strangler fig to extract InventoryService from the B2B monolith. After two months, InventoryService handles all inventory API traffic. However, BillingService (still in the monolith) continues to read the `inventory_reservations` table directly via the shared ORM. The team lead says: 'The extraction is complete — all API traffic goes to InventoryService.' A senior engineer says the extraction is not complete. Who is right and why?
Decompose by capability, not by layer
The antipattern that produces wrong seams is decomposing the monolith by technical layer: splitting the presentation layer, the business logic layer, and the data layer into separate services. This creates three services that implement one business capability — they are fragments of a workflow, not autonomous capabilities.
A service extracted by layer:
- Cannot be independently deployed (the logic service depends on the data service for every operation).
- Cannot be independently tested (the logic service has no behavior without the data service).
- Forces synchronous coupling for every request (three network hops where zero were needed).
A service extracted by business capability owns its full slice of the stack: its API, its logic, and its persistence. It is the vertical slice architectural approach applied to service decomposition.
An architect proposes decomposing the B2B billing monolith into three services: DataService (all database access), BusinessLogicService (all domain rules), and ApiService (all HTTP endpoints). Each request from the frontend would go to ApiService, which calls BusinessLogicService, which calls DataService. What structural problems does this decomposition create?
Sequence: what to extract first
Not all modules are equally good starting points for extraction. The strangler fig works best when the first extracted service:
Has low coupling to other monolith internals. If the billing capability only needs to call two internal APIs (and those calls can be wrapped behind interfaces), extraction is relatively contained. If it shares tables with every other module, extraction requires migrating every access point simultaneously.
Has high change frequency. A capability that releases changes twice a week while everything else releases monthly gains the most from independent deployment. Extracting a stable, rarely-changed capability doesn’t move the needle on deployment agility.
Is already modular internally. If the ordering capability in the monolith is a coherent module with a clear internal structure and owned tables, extraction is mostly plumbing. If it is entangled with shared code and tables, extraction requires refactoring before extraction.
Has clear ownership. A team that already owns the capability can own its service. Cross-team shared code is the hardest to extract because no one team can authorize the removal of the shared code.
The B2B billing platform is planning strangler fig extractions. The team has two candidates: (A) Notification module — sends emails and SMS, releases rarely, is called by every other module via a shared NotificationService class, reads no tables of its own (writes to `notifications` table), and its code is clean and self-contained. (B) Reporting module — generates invoices and billing reports, releases frequently, reads the `orders`, `invoices`, `customers`, and `charges` tables directly via ORM, and its code is heavily entangled with billing logic. Which module should be extracted first and why?
- 01What is the strangler fig pattern and why does it avoid the 'big bang' rewrite problem?
- 02Why is 'database-per-service' an ownership contract, not just a technology choice?
- 03What is wrong with decomposing a monolith by technical layer (presentation / logic / data as three services)?
The billing platform’s failed extraction moved the API without moving the data. The monolith’s ORM still owned the tables; the new OrderService had the endpoint but not the data. Every write was a race condition between the service and the monolith’s leftover code. The lesson: a service is not autonomous until it owns its persistence.
The strangler fig solves this incrementally. A proxy in front of the monolith routes traffic slice by slice to new services. Each slice is extracted with its data ownership transferred — the monolith’s direct table access replaced with API calls to the new service — before the next slice begins. At any point, the system is working; the monolith is simply smaller.
The right seam for extraction is a bounded context: a business capability that can be named without “and.” Decomposing by technical layer — splitting presentation, logic, and data into separate services — produces the worst possible outcome: distributed coupling with no autonomy. Decomposing by capability produces services that can be deployed, tested, and failed in isolation.
Start with the capability that has the lowest outbound coupling and the highest change frequency. Build the muscle for strangler fig extractions on an easy case, then apply it to the harder ones.
Practice
Start at the top. Tasks go easiest → hardest: recall a fact, apply it to a case, then a senior-level stretch. Open one, attempt it, then reveal.
Something unclear?
Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.