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:
- Hit the web server.
- Call a
ProductControllerin the monolith. - The
ProductControllercalls aProductServicewithin the monolith. - The
ProductServicequeries aProductsTablein the shared database. - It might also query an
InventoryTableand aPricingTablein the same database. - 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
productstable (and any related tables likecategories,product_attributes) intoproduct_db. - Action: Update the monolith’s
ProductServiceto read from this newproduct_dbinstead 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, sayGET /api/v1/products/{id}. This API will fetch data from the newproduct_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-serviceproject. This will be the actual microservice. - Action: Configure this
product-serviceto connect to its dedicatedproduct_db. - Action: Implement the
GET /api/v1/products/{id}endpoint withinproduct-service. This implementation will fetch data directly fromproduct_db. - Action: In the monolith’s
ProductService, change its data fetching logic. Instead of reading fromproduct_dbdirectly, it now calls theGET /api/v1/products/{id}endpoint exposed by theproduct-api(which is running theproduct-servicecode).
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 theproduct-serviceinstead of the monolith. - Action: Once traffic is confirmed to be flowing correctly to
product-service, you can remove theproduct-apiproject 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.