With the design principles as a checklist, Luis's team faces the practical question: where do you cut the monolith? Decomposing is not drawing six boxes named after the folders in the repository. It is finding the system's real seams (the places where code and data already are, or can be, loosely coupled), choosing a coherent cutting strategy, picking an extraction order that minimizes risk, and applying techniques that let you pull pieces out without stopping the store.

In this lesson we walk through the decomposition strategies (by business capability, by subdomain, by use cases versus resources, by teams), the techniques for discovering seams (introductory event storming, code dependency analysis and coupling analysis of the tables in the SQL schema from 01-05), the two most widely used safe-extraction techniques (strangler fig and branch by abstraction), the criteria for ordering the extraction, and what to do with shared utilities such as db/connection.js or services/email.js. We finish with the guided example: the techcorp-shop monolith decomposed into the six services of the target map, with the dependency diagram before and after. How the data is physically split (02-04) and how the single transaction is replaced (02-05) are left for the following lessons.

Contents

  1. What decomposing means (and what it does not)
  2. Decomposition strategies
  3. Techniques for discovering the seams
  4. Extracting without breaking: strangler fig and branch by abstraction
  5. In what order to extract: risk, value and dependencies
  6. What to do with shared utilities
  7. Guided example: techcorp-shop in six services

  1. What decomposing means (and what it does not)

Decomposing a monolith means redistributing responsibilities, code and data into autonomous units that satisfy the principles of 02-01. Three clarifications avoid frequent misunderstandings:

  • It is not "one service per folder". The fact that the monolith has controllers/ordersController.js does not guarantee that "orders" is a good boundary: as we saw, that function touches five areas. Folders are a clue, not the answer.
  • It is not "one service per table". That would be the entity service from 01-04. A service groups the tables that change together under one capability.
  • It is not a single act. It is an incremental process in which each extraction leaves the system working and delivers value on its own. If an extraction only makes sense "once they are all done", it is a big bang in disguise.

The outcome of decomposition is a plan: which services there will be, what each takes from the monolith, in what order they are extracted, and with which technique. That plan is this lesson's deliverable for TechCorp.

  1. Decomposition strategies

There are four common ways of deciding boundaries. They are not mutually exclusive: in practice they are combined, and one serves to validate another.

2.1 By business capability

You ask "what does the company do?" and answer with a list of capabilities that are stable over time: manage the catalog, control inventory, process orders, charge, serve customers, communicate with them. Each capability has its own business owner, vocabulary and pace of change (principle 3 in 02-01).

It is the most widely used strategy because capabilities change far less than technology or organization: TechCorp was charging orders ten years ago and will be charging them ten years from now, even if it changes payment provider three times.

2.2 By subdomain (DDD)

Domain-Driven Design starts from the domain (the whole business) and divides it into subdomains: parts of the problem with their own model and their own language. It usually produces boundaries very similar to business capabilities, but with two extra contributions:

  • It distinguishes core subdomains (where the company competes), supporting and generic ones, which helps decide where to invest design effort.
  • It detects that the same word ("product", "customer") means different things in different subdomains, and that this difference in meaning is a seam.

In this lesson we use DDD only as a cutting strategy; the rigorous definition of TechCorp's bounded contexts, the ubiquitous language and the context map are the content of 02-03.

2.3 By "verbs / use cases" versus "nouns / resources"

This is a granularity decision within any of the previous strategies:

Approach Organized around... Example When it works Risk
Nouns / resources Domain entities: product, order, payment. orders-service owns the Order entity and its whole lifecycle. When the entity has a rich lifecycle and the operations on it are cohesive. Degenerating into an entity service (CRUD with no business).
Verbs / use cases Processes: place an order, return an order, restock. A checkout-service that orchestrates the purchase end to end. When a process cuts across many entities and deserves its own owner. The "verb" service ends up knowing everyone's data (the same old createOrder is back, but over HTTP).

For TechCorp the choice is nouns as the base (orders, products, stock, payments, customers) and the "place an order" flow spread across them through events, with no separate "checkout" service. The reason will be seen in detail in 02-05: the flow is coordinated by choreography, and orders-service owns the order's state, not the other services' operations. If the flow became more complex (partial returns, orders with several shipments), an orchestrator service would be the moment to reconsider.

2.4 By teams

Sometimes the cut follows who is going to maintain what. It is Conway's Law used on purpose (02-01, section 9): if Payments and Notifications are going to belong to the same team, should they be one service or two? TechCorp's answer is two, because they have different paces of change and risk profiles (Payments touches money and an external payment provider; Notifications touches templates and email providers), but it is legitimate for the organization to influence the final granularity.

Strategy Question it answers Strength Weakness Use at TechCorp
Business capability What does the company do? Stable over time; understandable by the business. Can ignore real technical coupling. Basis of the cut.
Subdomain (DDD) Where does the meaning of words change? Detects subtle seams; prioritizes the core. Requires team training. Refinement in 02-03.
Verbs vs. nouns Entities or processes? Adjusts granularity. "Verbs" tend to hoard data. Nouns + events.
By teams Who maintains it? Aligns organization and system. Fossilizes the current organization. Final granularity adjustment.

  1. Techniques for discovering the seams

Strategies tell you how to think; techniques tell you where to look. TechCorp uses three, from most conversational to most mechanical.

3.1 Event storming (introductory level)

It is a workshop in which business and engineering reconstruct the domain on a wall with sticky notes. In its minimal version:

  1. Domain events (orange): relevant facts in the past tense. "Order created", "Stock reserved", "Payment confirmed", "Email sent", "Product published", "Price changed", "Customer registered", "Stock restocked".
  2. Commands (blue): the intention that triggers the event. "Create order", "Reserve stock", "Charge".
  3. Actors and external systems (yellow / pink): customer, warehouse operator, payment provider, email provider.
  4. They are arranged on a timeline and you look for pivotal events: those after which the vocabulary changes and the people talking change.

What is discovered for TechCorp, in summary:

flowchart LR
    subgraph Catalog
        E1[Product published] --> E2[Price changed]
    end
    subgraph Customers
        E3[Customer registered]
    end
    subgraph Orders
        E4[Order created] --> E7[Order confirmed]
        E4 --> E8[Order cancelled]
    end
    subgraph Inventory
        E5[Stock reserved]
        E9[Stock restocked]
        E10[Stock released]
    end
    subgraph Payments
        E6[Payment confirmed]
        E11[Payment refunded]
    end
    subgraph Notifications
        E12[Email sent]
    end
    E4 -.-> E5 -.-> E6 -.-> E7 -.-> E12

The groups of events that share vocabulary and actor are the service candidates; the dashed arrows between groups are the events that will cross boundaries (the order.created, stock.reserved, payment.confirmed, order.confirmed and order.cancelled from 01-05 come literally off this wall). Notice that two events appear that we had not named yet, "Stock released" and "Payment refunded": they are the compensations the saga in 02-05 will need. The workshop also reveals that "Price changed" is of no interest to Orders (an order stores the price at the time of purchase), which anticipates a data decision in 02-03.

3.2 Code dependency analysis

The second technique is mechanical: who imports whom inside the monolith's repository. A require/import graph (tools such as madge or dependency-cruiser generate it in seconds) shows the modules with many incoming dependencies (hard to extract because everyone uses them) and outgoing ones (hard to extract because they use everyone).

# At the root of the techcorp-shop monolith: dependency graph between modules
npx madge --extensions js --json ./ > dependencies.json
# Modules with the most incoming dependencies (who does everybody depend on?)
npx madge --extensions js --summary ./

Summary of what Luis's team sees in techcorp-shop:

Module Depends on (outgoing) Used by (incoming) Reading
db/connection.js All controllers Shared technical utility; handle as in section 6.
controllers/ordersController.js db, paymentProvider, email routes/orders.js Few module dependencies, but a huge number of table dependencies (see 3.3): the coupling is in the SQL, not in the requires.
controllers/catalogController.js db routes/catalog.js Autonomous at the code level. Good first candidate.
services/email.js ordersController, customersController, shippingController Utility with business logic inside (templates). Candidate for a service.
services/paymentProvider.js ordersController, returnsController External adapter; payments-service will take it.

The lesson of this table: in a monolith with a shared database, the require graph is misleading. Modules look independent because the real coupling goes through the tables. Hence the third technique.

3.3 Table coupling analysis

You build a table × area matrix marking who reads (R) and who writes (W) each table, based on the monolith's SQL code. For the schema from 01-05:

Table Customers Catalog Inventory Orders Payments Notifications Natural owner
customers W R (createOrder) R (email, name) Customers
products W R (FK) R (name, price) Catalog
product_attributes W Catalog
stock W W (createOrder reserves and deducts) Inventory
orders W R (order_id) R (new email) Orders
order_lines R ("best sellers" report) W Orders
payments W (createOrder inserts) W Payments
notifications W Notifications

How to read it:

  • A table with a single W and no foreign R (product_attributes, notifications) is a clean seam: it is extracted with its area without touching anything else.
  • A table with one own W and several foreign Rs (customers, products, orders) is extracted with its owner; the foreign reads become calls to the contract or read-only copies (02-04).
  • A table with two Ws (stock, payments) is a design problem, not just an extraction problem: today createOrder writes to Inventory and Payments tables. Before extracting, you have to give the write back to its owner (Orders will ask Inventory to reserve, rather than reserving itself). That is the first refactoring Luis's team will do inside the monolith, before moving anything.

This matrix is the most valuable tool of the whole lesson: it turns "I think orders is very coupled" into "orders writes to two foreign tables and reads two others", which is something you can plan around.

  1. Extracting without breaking: strangler fig and branch by abstraction

Once what to extract is decided, you need how to do it with the store running. Two techniques cover most cases. Here we present them as decomposition tools; the full account of TechCorp's migration, step by step, is in 08-01.

4.1 Strangler fig

A facade (at TechCorp, the API Gateway on port 8080) is placed in front of the monolith. At first, all traffic goes to the monolith. When a new service is ready, the facade redirects only its routes to the service; the rest keeps going to the monolith. Little by little, the monolith receives fewer routes until it is empty and can be switched off.

flowchart LR
    C[Customer] --> GW[API Gateway :8080]
    GW -- "/products/*  (already extracted)" --> CAT[catalog-service :3001]
    GW -- "/orders/*, /customers/*, /payments/* (still)" --> MONO[techcorp-shop monolith]
    CAT --> M1[(MongoDB catalog)]
    MONO --> PG[(PostgreSQL techcorp)]

Advantages: reversible (if the new service fails, the facade points back to the monolith), incremental and visible to the business (every migrated route is a milestone). Requirement: the facade must exist before the first extraction, which makes it the plan's first technical deliverable.

4.2 Branch by abstraction

It serves the internal dependencies the facade cannot see: for example, createOrder gets prices by reading the products table. You cannot redirect "by route"; you have to change the monolith's code. The technique:

  1. Introduce an abstraction in front of the functionality (a ProductsRepository interface).
  2. Make the existing code use it (the initial implementation still reads the table).
  3. Write a second implementation that uses the new service (HTTP client for catalog-service).
  4. Switch between the two through configuration (a feature flag), first in testing, then in production, with immediate rollback if something fails.
  5. Delete the old implementation.
// monolith/orders/productsRepository.js  (branch by abstraction, skeleton)
// Steps 1-2: the abstraction. createOrder stops doing direct SQL and calls this instead.
class SqlProductsRepository {                   // old implementation: local table
  async getByIds(ids) {
    const { rows } = await db.query(
      'SELECT id AS "productId", name, price FROM products WHERE id = ANY($1) AND active', [ids]);
    return rows;
  }
}

class HttpProductsRepository {                  // new implementation: the service's contract
  async getByIds(ids) {
    // The HTTP client details (timeouts, retries) are covered in 04-04; here, only the contract.
    return await catalogClient.get('/products', { ids: ids.join(',') });
  }
}

// Step 4: the switch by configuration. Same contract, two sources.
function createProductsRepository(config) {
  return config.REMOTE_CATALOG === 'true'
    ? new HttpProductsRepository()
    : new SqlProductsRepository();
}

module.exports = { createProductsRepository };

Notice that both implementations return the same shape (productId, name, price): that shape is the embryo of the catalog's contract, and making it explicit inside the monolith is already a decomposition step even though not a single line of server has been moved.

Technique Acts on Reversible Used for
Strangler fig Incoming traffic (HTTP routes) Yes (routing change) Extracting externally exposed functionality.
Branch by abstraction Internal code dependencies Yes (feature flag) Replacing an internal collaborator with a remote service.

  1. In what order to extract: risk, value and dependencies

Not all services are extracted at once, nor in just any order. TechCorp scores each candidate on three axes:

  • Value: how much it relieves a real problem among the five from 01-05 (scaling during campaigns, deployments, cascading incidents, forced data model, teams stepping on each other).
  • Risk: how much business is at stake if the extraction goes wrong, and how much delicate logic (money, identity) is touched.
  • Dependencies: how many areas depend on it (incoming) and how many it depends on (outgoing). Few dependencies, easy extraction.
Service Value Risk Incoming / outgoing dependencies Score and order
catalog-service Very high: it is what saturates ×20 during campaigns and what suffers from the key-value model. Low: it is mostly read; if it fails, the store shows a search error, it does not lose charges. Incoming: Orders (name and price). Outgoing: none. 1st
inventory-service High: it scales with campaigns and is the first step of the saga. Medium: a stock error sells what is not there. Incoming: Orders. Outgoing: none (it only needs productId). 2nd
notifications-service High: it is the source of the memory leak that took down charges; pulling it out isolates the incident. Low: if it fails, an email is delayed. Incoming: Orders, Customers. Outgoing: reads customers and orders (replaced by data in the event). 3rd
payments-service High: it isolates the external payment provider and its latency; it makes it possible to narrow the PCI scope. High: money. Incoming: Orders. Outgoing: external payment provider. 4th
orders-service Very high: it is the core, the one that changes the most and the one Luis wants to deploy daily. High: it is the sales flow. Incoming: Notifications, Payments, reports. Outgoing: all (customers, catalog, inventory, payments). 5th: extracted once its collaborators already exist as services and the saga is designed.
customers-service Medium: authentication is delegated to Keycloak, so the monolith already gets lighter without extracting it. High: identity and personal data; customer_id is everywhere. Incoming: all areas. Outgoing: none. 6th: last; in the meantime, the residual monolith serves /customers.

Three reasons sum up why the catalog goes first: it is where the most expensive problem lies (the campaign outages), it is the lowest-risk extraction (it touches neither money nor the order flow on its critical path) and it depends on nobody: only Orders depends on it for two fields, and that is solved with the abstraction from section 4.2. Extracting it first also delivers an early win that justifies the investment to Marta and trains the platform team on the full pipeline (Docker, Kubernetes, canary) with the least dangerous service.

And one reason why orders does not go first even though it is the core: extracting first the service that depends on everyone would force building six contracts at once, or having orders-service keep reading the monolith's database, that is, being born as a distributed monolith.

  1. What to do with shared utilities

Every monolith has code that "everybody uses". In techcorp-shop: db/connection.js, services/email.js, services/paymentProvider.js, plus minor utilities (logging, request validation, error formatting). There are four possible destinations, and choosing badly creates new coupling:

Utility Nature Recommended destination Why
db/connection.js (PostgreSQL pool) Technical, no business, 30 lines Copy into each service (or project template). Each service has its own DB and its own configuration; a shared library for 30 lines couples deployments for nothing.
Logging, HTTP error formatting, JSON validation Technical, no business Versioned internal library (@techcorp/common-http), published in a private registry, with semantic versions. A shared library is accepted only for code with no business logic; each service chooses when to update the version, so there is no deployment coupling.
services/email.js (templates, SMTP sending) Contains business (which emails exist, what they say) Becomes the core of notifications-service. Not shared. If it were a library, each service would send its own emails and the memory leak would remain inside all of them. As a service, it reacts to events and isolates the failure.
services/paymentProvider.js (payment provider adapter) External adapter with charging rules Taken exclusively by payments-service. Only one service should talk to the payment provider; the rest ask to "charge" through the contract.
Business models / DTOs (models/Order.js, models/Product.js) Business Never shared. Each service defines its own. Sharing domain models is the "shared layer" from 01-04: a change in Product redeploys everyone. Each context has its own "product" (02-03).

The rule TechCorp adopts: technical code is shared as a versioned library; business code is not shared, it becomes a service or is consciously duplicated. Duplicating a 5-field DTO in two services is cheap; sharing it costs the autonomy of both.

  1. Guided example: techcorp-shop in six services

We apply all of the above. TechCorp's decomposition plan:

Step 1. Inventory of the monolith. Folders, modules and tables (01-05), and the table × area matrix (3.3).

Step 2. Cut by business capability, validated with the event groups from the event storming (3.1) and with the table matrix: six capabilities, six services. It matches the six functional areas, with two nuances the matrix has uncovered: stock reservation will be done by Inventory (today createOrder does it) and payment recording will be done by Payments (today createOrder does it).

Step 3. Allocation of code and data (data is detailed in 02-04; here, the assignment):

Service Takes from the monolith Tables it will own Stops doing
catalog-service (3001) controllers/catalogController.js, routes/catalog.js, search products, product_attributes (which in 02-04 will become MongoDB documents)
inventory-service (3006) Reservation/release logic currently spread across createOrder and warehouseController.js stock (+ new reservations table)
orders-service (3002) ordersController.js, routes/orders.js orders, order_lines Reading customers and products; writing stock and payments; sending email.
payments-service (3003) services/paymentProvider.js, returnsController.js payments
notifications-service (3005) services/email.js, templates Minimal log of sends (notifications) Reading customers and orders (it will receive what it needs in the events).
customers-service (3004) customersController.js, profile, addresses customers Authenticating (delegated to Keycloak, 07-01).

Step 4. Dependency diagram before and after.

Before: every module depends on every table through a single connection pool. The graph is, in practice, complete.

flowchart TB
    subgraph MONO[techcorp-shop monolith - one process, one deployment]
        PC[ordersController]
        CC[catalogController]
        CLC[customersController]
        AC[warehouseController]
        DC[returnsController]
        CO[services/email]
        PP[services/paymentProvider]
        PC --> CO
        PC --> PP
        DC --> PP
        CLC --> CO
    end
    subgraph PG[(PostgreSQL techcorp - one schema)]
        T1[customers]
        T2[products]
        T3[stock]
        T4[orders / order_lines]
        T5[payments]
        T6[notifications]
    end
    PC --> T1
    PC --> T2
    PC --> T3
    PC --> T4
    PC --> T5
    CC --> T2
    CC --> T4
    CLC --> T1
    AC --> T3
    AC --> T2
    DC --> T5
    DC --> T4
    CO --> T6
    CO --> T1

After: each service depends only on its own storage; between services there are only contracts (solid arrows: synchronous API calls) and events (dashed arrows: publish/subscribe). The how of those arrows is module 3.

flowchart TB
    GW[API Gateway :8080] --> CAT[catalog-service :3001]
    GW --> CUS[customers-service :3004]
    GW --> ORD[orders-service :3002]
    CAT --> BDC[(MongoDB catalog)]
    CUS --> BDL[(PG customers)]
    ORD --> BDP[(PG orders)]
    INV[inventory-service :3006] --> BDI[(PG inventory)]
    PAY[payments-service :3003] --> BDG[(PG payments)]
    NOT[notifications-service :3005]
    ORD -- "GET /products (name, price)" --> CAT
    ORD -- "GET /customers/{id} (exists, email)" --> CUS
    ORD -. "order.created / order.confirmed / order.cancelled" .-> MQ[(RabbitMQ)]
    MQ -. "order.created" .-> INV
    INV -. "stock.reserved" .-> MQ
    MQ -. "stock.reserved" .-> PAY
    PAY -. "payment.confirmed" .-> MQ
    MQ -. "payment.confirmed" .-> ORD
    MQ -. "order.confirmed / order.cancelled" .-> NOT
    PAY --> EXT1[External payment provider]
    NOT --> EXT2[Email / SMS]

Compare the two graphs with the coupling table from 02-01: in the first, everything is data and deployment coupling; in the second, there is only contract coupling (two synchronous calls from Orders) and event coupling. One deliberate temporal coupling remains (Orders queries Catalog and Customers synchronously when creating the order); in 02-04 we will see the alternative of replicating that data, and in 06-03 how to protect those calls.

Step 5. Extraction order and technique. Catalog (strangler on the /products routes + branch by abstraction for prices in createOrder), Inventory (branch by abstraction for reservation), Notifications (the monolith starts publishing events and the service consumes them), Payments (branch by abstraction for paymentProvider), Orders (strangler on /orders, already with the saga from 02-05) and Customers (strangler on /customers; the monolith is switched off).

Common Mistakes and Tips

  • Cutting by repository folders. Folders reflect how the code was organized years ago, not the business seams. Use the table matrix: it does not lie.
  • Trusting only the require graph. In a monolith with a shared DB, the coupling is in the SQL. A module with no code dependencies can write to four foreign tables.
  • Extracting the core first "because it is what matters". It is what depends on everyone; extracting it first forces it to be born coupled. Start with what depends on nobody and delivers visible value.
  • Turning the utilities into one big shared library. Every library update redeploys everyone: it is deployment coupling in its purest form. Technical code only, versioned, and each service decides when to update.
  • Extracting without a facade. Without a gateway there is no strangler fig and no cheap rollback. The gateway is the first piece, not the last.
  • Tip: before moving code into a new service, fix the coupling inside the monolith (give writes back to their owner, introduce the abstractions). A well-modularized monolith decomposes in weeks; a tangled one, in years.

Exercises

Exercise 1: Reading the matrix

Using the table × area matrix from section 3.3: (a) which two tables require a refactoring inside the monolith before their service can be extracted, and what does it consist of? (b) Which table is the cleanest extraction and why? (c) Notifications reads customers and orders; how will it get that data once it is a service, without reading foreign tables?

Exercise 2: A seventh service

Marta wants to add "promotions" (discount coupons, the case from 01-03) during the migration. Apply the value, risk and dependency criteria and decide: is it built directly as promotions-service, or is it added to the monolith first? Where would it fit in the extraction order? Justify in 5-8 lines.

Exercise 3: Branch by abstraction for stock reservation

Write the skeleton (without implementing details) of the abstraction that would let createOrder stop writing to stock and instead ask inventory-service for the reservation, with a configuration switch. Define the abstraction's contract (method name, input, output) and indicate what each implementation would return when there is no stock.

Solutions

Exercise 1

(a) stock and payments: both have two writers. createOrder reserves and deducts stock, and inserts into payments. Before extracting Inventory and Payments, that logic has to be moved to its owning modules inside the monolith (for example, warehouse.reserve(...) and payments.recordCharge(...)), so that createOrder only invokes and does not write. Afterwards, those invocations are replaced by calls to the service using branch by abstraction. (b) product_attributes (and notifications): a single writer, no foreign reader. It is extracted with Catalog without anyone else noticing; besides, its key-value model is the reason for moving the catalog to MongoDB. (c) By receiving them in the event: order.confirmed will include the orderId, the total and the minimal customer data needed for the email (email, name), which orders-service will have obtained through the Customers contract. Alternatively, Notifications could query GET /customers/{id} upon receiving the event; both options are compared in 02-04. What it will never do is SELECT on customers.

Exercise 2

Value: medium-high for the business (campaigns), but it solves none of the five problems from 01-05. Risk: low in itself, but high because of the timing: adding a new service while the core is being migrated doubles the workload. Dependencies: promotions would need to know products (what it applies to) and orders (where it applies), that is, it depends on two services that are not yet extracted. A reasonable decision: build it inside the monolith as a well-isolated module (its own coupons table, a Promotions.computeDiscount(lines) abstraction used by createOrder), honoring the principles of 02-01 from day one so that its later extraction is trivial. It would fit in the order between Payments and Orders (position 4-5): after Catalog and Inventory (its dependencies) exist and before Orders is extracted with the definitive saga, so that the discountApplied field of order.confirmed is born already in the contract. If the business does not need it urgently, even better: after Orders, with the system stable.

Exercise 3

// Abstraction contract: reserve(orderId, lines) -> { ok: true, reservationId }
//                                                | { ok: false, reason: 'OUT_OF_STOCK', productId }
class SqlStockReservation {                // old implementation: the monolith's local table
  async reserve(orderId, lines) {
    // UPDATE stock SET reserved = reserved + quantity WHERE ... AND quantity - reserved >= quantity
    // If any UPDATE affects 0 rows -> return { ok: false, reason: 'OUT_OF_STOCK', productId }
    // If everything goes well -> { ok: true, reservationId: `local-${orderId}` }
  }
  async release(reservationId) { /* UPDATE stock SET reserved = reserved - quantity ... */ }
}

class HttpStockReservation {               // new implementation: inventory-service contract
  async reserve(orderId, lines) {
    // POST /reservations { orderId, lines } -> 201 { reservationId }  or  409 { reason: 'OUT_OF_STOCK', productId }
    // Returns the same shape as the SQL implementation.
  }
  async release(reservationId) { /* DELETE /reservations/{reservationId} */ }
}

function createStockReservation(config) {
  return config.REMOTE_INVENTORY === 'true' ? new HttpStockReservation() : new SqlStockReservation();
}

The essential part: the two implementations return the same shape on success and on failure ({ ok, reservationId } / { ok: false, reason }), so that createOrder cannot tell where the reservation comes from; and the abstraction already includes the inverse operation (release), because as soon as the reservation leaves the single transaction, compensations will be needed (02-05).

Conclusion

Decomposing a monolith is analysis work before it is coding work. We have covered the strategies (business capability as the base, DDD subdomains as refinement, nouns versus verbs to adjust granularity, teams as the final adjustment), the techniques for discovering seams (event storming, the code dependency graph and, the most revealing in a monolith with a shared DB, the table × area coupling matrix, which has uncovered that createOrder writes to stock and payments without owning them), the safe-extraction techniques (strangler fig behind the gateway and branch by abstraction with a configuration switch) and the ordering criteria (value, risk, dependencies) that put the catalog first and orders and customers last. We have also decided the fate of the shared utilities: technical code as a versioned library; business code as a service (email.jsnotifications-service, paymentProvider.jspayments-service) or deliberately duplicated. The result is TechCorp's plan: six services, each with its modules and its tables, and a dependency graph that has gone from "everyone against every table" to "contracts and events".

That plan still has boundaries drawn with a broad brush. The next lesson sharpens them with strategic DDD: we will define TechCorp's bounded contexts, see why "product" and "customer" do not mean the same thing in each context, draw the context map with its relationship patterns, and decide what an aggregate is (and why stock is not part of the Order aggregate).

Microservices Course

Module 1: Introduction to Microservices

Module 2: Microservice Design

Module 3: Communication between Microservices

Module 4: Implementing Microservices

Module 5: Deployment and Orchestration

Module 6: Monitoring and Maintenance

Module 7: Security in Microservices

Module 8: Case Studies and Practical Examples

© Copyright 2026. All rights reserved