Extracting services from a monolith isn’t about carving out independent pieces of code; it’s about identifying and isolating business capabilities that can operate with minimal entanglement.

Let’s say we have a monolithic e-commerce application. It handles everything: user authentication, product catalog, order processing, inventory management, and payment. We want to extract the "Product Catalog" service.

Here’s what that might look like in a simplified, hypothetical scenario.

Imagine a request to view a product page. In the monolith, this single request might:

  1. Hit the web server.
  2. Call a ProductController in the monolith.
  3. The ProductController calls a ProductService within the monolith.
  4. The ProductService queries a ProductsTable in the shared database.
  5. It might also query an InventoryTable and a PricingTable in the same database.
  6. Finally, it returns the aggregated data back through the controller to the web server.

This is the entangled state. Now, let’s start extracting.

Step 1: Data Separation

The first, and often hardest, step is to separate the data. The Product Catalog needs its own database.

  • Action: Create a new database instance (e.g., PostgreSQL, MySQL, or even a document store like MongoDB) specifically for the product catalog. Let’s call it product_db.
  • Action: Write a script to migrate existing product data from the monolith’s shared products table (and any related tables like categories, product_attributes) into product_db.
  • Action: Update the monolith’s ProductService to read from this new product_db instead of the old shared database. This is a crucial transitional step. The monolith now depends on the new, separate product database.

Why it works: This forces us to define the boundaries of the product data. The monolith is no longer the sole owner of this data; it’s now a consumer.

Step 2: API Exposure

The monolith needs a way to ask the (soon-to-be) independent product service for data. We’ll create an API for this.

  • Action: In the monolith’s existing ProductService, modify it to also expose a REST API endpoint, say GET /api/v1/products/{id}. This API will fetch data from the new product_db.
  • Action: Create a new, minimal "product-api" project. This project will simply proxy requests to the monolith’s new GET /api/v1/products/{id} endpoint.

Why it works: We’re creating an interface. The monolith is now acting as a gateway for itself to the product data it previously managed directly. This seems redundant, but it’s a stepping stone. The "product-api" project is the nascent microservice.

Step 3: Feature Parity in the New Service

Now, we build the actual microservice, but it will initially depend on the monolith for certain functionalities.

  • Action: Create a new product-service project. This will be the actual microservice.
  • Action: Configure this product-service to connect to its dedicated product_db.
  • Action: Implement the GET /api/v1/products/{id} endpoint within product-service. This implementation will fetch data directly from product_db.
  • Action: In the monolith’s ProductService, change its data fetching logic. Instead of reading from product_db directly, it now calls the GET /api/v1/products/{id} endpoint exposed by the product-api (which is running the product-service code).

Why it works: The new service is running independently, accessing its own data. The monolith is now a client of this new service, demonstrating the decoupling. The product-api is essentially the new microservice, and the monolith is consuming it.

Step 4: Redirecting Traffic

The final step is to send all external requests for product data to the new microservice.

  • Action: Update your API Gateway or Load Balancer. For any incoming requests matching GET /api/v1/products/*, route them directly to the product-service instead of the monolith.
  • Action: Once traffic is confirmed to be flowing correctly to product-service, you can remove the product-api project and the corresponding API endpoint from the monolith.

Why it works: All external interaction with the product catalog now goes through the dedicated microservice, completely bypassing the monolith for this functionality.

The One Thing Most People Don’t Know

The most challenging part of this process isn’t the code or the database migration; it’s managing the state transitions and ensuring transactional integrity across service boundaries during the extraction. For example, when a product is updated, the monolith might need to update its internal cache, the product service needs to update its database, and the inventory service (another potential microservice) needs to know about stock changes. Orchestrating these distributed transactions, especially if you’re not using event sourcing or sagas from the start, can lead to subtle data inconsistencies if not handled with extreme care. Often, a period of dual writes or eventual consistency is necessary, which introduces its own set of complexities.

Next Up: You’ll likely encounter issues with distributed tracing or need to implement a robust API Gateway to manage inter-service communication.

Want structured learning?

Take the full Monolith course →