Refactoring a Monolithic Inventory System to Distributed Microservices with CQRS

Facing rapid growth and stability issues, the team transformed a monolithic inventory platform into a distributed microservice architecture, employing functional and business decomposition, CQRS principles, and careful code refactoring to improve availability, scalability, and maintainability while addressing challenges like distributed transactions and data consistency.

dbaplus Community
dbaplus Community
dbaplus Community
Refactoring a Monolithic Inventory System to Distributed Microservices with CQRS

Background

Before discussing architecture styles, the article asks what architecture is and why it is chosen to solve specific problems. Architecture is defined as an abstract structure composed of components and their dependencies, guiding technology and middleware selection to meet business requirements.

Purpose of Choosing an Architecture Style

The author’s "Three‑Better" principle aims to reduce cost, accelerate releases, and improve system stability. A good architecture should satisfy functional needs while enhancing non‑functional attributes such as scalability and reliability.

Monolithic Architecture

Types

Big‑Mud‑Pot monolith – no layering, hard to split later.

Layered monolith – typical MVC three‑tier structure.

Modular monolith – layered monolith that evolves by adding business modules.

Advantages

Simple development and testing.

Easy large‑scale changes.

Straightforward deployment.

Horizontal scaling is trivial.

Disadvantages

Codebase bloat.

Complexity scares developers.

Slower development speed.

Long build‑to‑deploy cycles.

Difficult to scale.

Stability cannot be guaranteed.

Risk of being locked into an outdated tech stack.

These drawbacks conflict with the "Three‑Better" principle, especially as the system grows.

Case Study: Inventory System

Initially the system was a single monolithic service providing inventory maintenance, query, and deduction capabilities. It consisted of modules such as API, common, dao, domain, innerApi, router, rpc, service, web, and worker. Two services were deployed: an API for external queries and a web backend for management.

Monolith module diagram
Monolith module diagram

During a promotion in June 2015 the web management platform crashed due to memory exhaustion caused by massive inventory imports, exposing the monolith’s inability to handle sudden traffic spikes and high concurrency.

Failure incident
Failure incident

Distributed Architecture

Pros

High availability.

High scalability.

Strong fault tolerance.

Better code readability.

Simpler maintenance.

Cons

Increased service count and learning cost.

Technology‑stack upgrades become more complex.

Distributed transaction handling.

RPC communication overhead.

The inventory system required high availability, high concurrency, and strong data consistency, making a distributed approach necessary.

Migration from Monolith to Distributed

1. Functional Decomposition

The team first split the system by functional groups (e.g., inventory initialization, maintenance, deduction, and alerts). Each group was deployed to its own cluster without major code changes, improving fault isolation.

Functional split diagram
Functional split diagram

2. Business Decomposition

Guidelines for business splitting:

Base the split on business domain boundaries.

Minimize overlapping dependencies.

Assess impact and keep modules small.

Avoid changing business logic during the split (except for bug fixes).

Events such as "stock initialization", "stock update", "stock deduction", and "stock alerts" were identified as independent workflows.

Event‑driven decomposition
Event‑driven decomposition

3. Refactoring the Code Base

The original SkuMainServiceImpl class violated the Dependency Inversion Principle and mixed read/write logic, creating a God class. The refactor introduced a CQS (Command‑Query Separation) layer and a dedicated business layer.

@Service
public class SkuMainServiceImpl implements SkuMainService {
    private static final Logger LOGGER = LoggerFactory.getLogger(SkuMainServiceImpl.class);
    @Resource private SkuMainDao skuMainDao;
    @Resource private ZkConfManagerCenterService zkConfManagerCenterService;
    @Resource private ProductImagesService productImagesService; // same‑level reference, violates DIP
    @Resource private MqService mqServiceImpl;
    @Value("${system.group.environment}") private String systemGroupEnvironment;
    /**
     * Problem: service layer aggregates too much business logic, making inversion impossible.
     */
    public void editorSaveProduct(SkuMainInfoMQEntity skuMainInfoMQEntity) throws Exception {
        // ... implementation ...
    }
}

After refactoring, the business logic moved to SkuMainBusinessServiceImpl, and separate read/write services were introduced.

@Service
public class SkuMainBusinessServiceImpl implements SkuMainBusinessService {
    private static final Logger LOGGER = LoggerFactory.getLogger(SkuMainBusinessServiceImpl.class);
    @Resource private ZkConfManagerCenterService zkConfManagerCenterService;
    @Resource private MqService mqService;
    @Resource private SkuMainReadservice skuMainReadservice;
    @Resource private SkuMainWriteservice skuMainWriteservice;
    @Value("${system.group.environment}") private String systemGroupEnvironment;
    public void editorSaveProduct(SkuMainInfoMQEntity skuMainInfoMQEntity) throws Exception {
        // ... same logic but delegating to read/write services ...
    }
}

Read and write responsibilities were further isolated into dedicated services, illustrated below.

Read service
Read service
Write service
Write service

4. Applying CQRS in the Distributed System

Following the functional split, the team built a CQRS‑based stock center (named CQRS‑StockCenter). Commands from the business layer write to a task table; a scheduler reads the table and updates Redis; reads are served directly from Redis, ensuring low‑latency queries.

Each inventory modification creates a write task.

A scheduled job syncs tasks to Redis.

Read services fetch stock data from Redis, guaranteeing strong consistency.

CQRS‑StockCenter flow
CQRS‑StockCenter flow

The design heavily relies on Redis for both read and write paths, achieving real‑time consistency while isolating failures.

5. Distributed Transactions

Local transactions are straightforward with a single database, but distributed transactions across services are challenging. Traditional solutions like JTA/JTS require XA compliance, which many modern data stores (MongoDB, RabbitMQ, Kafka) do not support. The article notes the trade‑offs and suggests using eventual consistency patterns where strict ACID is not feasible.

Conclusion

The migration demonstrates that architecture decisions must be driven by business needs. By iteratively applying functional decomposition, business‑level splitting, CQS refactoring, and CQRS patterns, the inventory system achieved higher availability, scalability, and maintainability, while acknowledging the inherent complexities of distributed transactions.

Author: 树洞君 (source: Juejin)

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

architectureMicroservicesrefactoringCQRS
dbaplus Community
Written by

dbaplus Community

Enterprise-level professional community for Database, BigData, and AIOps. Daily original articles, weekly online tech talks, monthly offline salons, and quarterly XCOPS&DAMS conferences—delivered by industry experts.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.