How DDD Can Rescue a Decaying Warehouse Management System

This article examines the chronic decay of a complex warehouse inbound/outbound system, analyzes its tangled business logic and chaotic architecture, and presents a domain‑driven design solution with onion architecture, CQRS, and practical implementation steps to achieve clearer boundaries, higher cohesion, and sustainable evolution.

Yanxuan Tech Team
Yanxuan Tech Team
Yanxuan Tech Team
How DDD Can Rescue a Decaying Warehouse Management System

System Background

Teddy inbound/outbound system is the core of inventory flow in the strict selection supply chain, connecting dozens of upstream and downstream systems for order fulfillment, procurement replenishment, inter‑warehouse transfer, and special warehousing scenarios.

The system, originally built in 2016, has undergone rapid iterations for three years, accumulating heavy technical debt, unclear business boundaries, and a tangled codebase with inconsistent styles.

Business complexity, unclear boundaries

Early wild growth led to vague boundaries; over time, many unused logics and overly complex flows persisted, causing regret among product managers.

Chaotic architecture, scattered functions

With 220,000 lines of code lacking proper layering, dependencies are tangled and code smells such as circular queries, scattered functionalities, long methods, and unclear service layers are abundant.

Delivery efficiency uncontrollable

Unclear scope and inaccurate effort estimation lead to high risk in delivery quality and speed.

What to do?

The team decided to rebuild from scratch, but acknowledges that a complete rewrite is not always the best solution; continuous incremental optimization is preferable.

Design and Implementation

Solution selection

The primary requirement is clear business boundaries, reasonable architecture, cohesive functionality, and long‑term maintainability. Two options were considered: (1) traditional table‑centric modular layering, and (2) domain‑driven design (DDD) with domain models decoupled from database tables. DDD was chosen for its business‑oriented focus and high cohesion.

Implementation

Step 1 – Modeling: Collaborate with product, development, and testing to build a ubiquitous language and domain model using use‑case analysis, defining events, commands, and bounded contexts.

Step 2 – Implementation: Guide architecture and coding with the domain model.

The overall architecture follows an onion model with CQRS, where the application layer depends on the infrastructure layer, not vice versa, and a shared package holds common utilities, enums, exceptions, and DTOs.

Domain layer : Contains business aggregates, entities, and domain capabilities; defines gateway interfaces for persistence.

Infrastructure layer : Implements persistence gateways, handling DO‑PO conversion.

Application layer : Depends on domain and infrastructure, adapts external adapters, provides query and command capabilities, and uses the Chain‑of‑Responsibility pattern for logic orchestration.

Interface layer : Exposes HTTP, RPC, etc., to external services.

Shared package : Holds common utilities, exception classes, and DTOs.

Example command execution flow:

Problems encountered

1. Designing aggregates and entities in a legacy‑burdened refactor

Top‑down design clashes with existing data structures, causing heavy conversion work. A hybrid approach keeps service boundaries while reusing existing tables for entities, simplifying conversion and preserving reusable code.

2. Can thin logic orchestration really be achieved?

Complex business processes require more than simple updates; they involve third‑party calls and messaging. Introducing a Chain‑of‑Responsibility pattern structures these steps into reusable nodes, keeping the command’s entry point singular.

3. Persisting without a central DB authority

Each entity carries a state tag (read, update, insert). A wrapper object holds the PO and its state, providing a template method that executes the appropriate insert or update, eliminating scattered if‑else persistence logic.

4. Simplifying the Query side of CQRS

Breaking the onion dependency allows the application layer to use PO objects directly from the infrastructure layer, avoiding redundant DTO conversions while accepting the trade‑off of tighter coupling to the database.

5. Why a shared dependency package?

It resolves common dependencies between new and legacy code, housing utilities, exception classes, and DTOs used across layers.

Conclusion

Domain‑driven design accurately reflects business models and provides a solid architectural structure with high‑cohesion aggregates, helping to prevent logic scattering and architectural chaos. However, it incurs significant learning and development costs, may not suit simple scenarios, and requires pragmatic trade‑offs during implementation.

architectureBackend DevelopmentDDDCQRSonion architecture
Yanxuan Tech Team
Written by

Yanxuan Tech Team

NetEase Yanxuan Tech Team shares e-commerce tech insights and quality finds for mindful living. This is the public portal for NetEase Yanxuan's technology and product teams, featuring weekly tech articles, team activities, and job postings.

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.