Refactoring a Legacy Warehouse System with Domain‑Driven Design

This article examines the chronic decay of a long‑standing warehouse inventory system, identifies its tangled business boundaries, chaotic architecture, and uncontrolled delivery efficiency, then presents a domain‑driven design solution with clear modeling, onion‑style layering, CQRS, and practical implementation steps to restore maintainability.

NetEase Yanxuan Technology Product Team
NetEase Yanxuan Technology Product Team
NetEase Yanxuan Technology Product Team
Refactoring a Legacy Warehouse System with Domain‑Driven Design

System Background

The Teddy warehouse inbound/outbound system manages core inventory flows and connects dozens of upstream and downstream services for order fulfillment, procurement, inter‑warehouse transfer, and special stock movements. Built in 2016, the codebase has grown to over 220 k lines with tangled dependencies, inconsistent coding standards, and undocumented business logic.

Key Problems

Complex business, unclear boundaries : Aggressive feature growth left many obsolete or unused logic and overly complex workflows.

Chaotic architecture, scattered functionality : Circular queries, long methods, ambiguous service layers, and fuzzy module boundaries.

Uncontrolled delivery efficiency : Inaccurate effort estimation and risky delivery quality.

Adopted Approach

Instead of a full rewrite, the team selected a Domain‑Driven Design (DDD) approach to clarify business boundaries, achieve a reasonable architecture, and keep functionality cohesive for long‑term iteration.

Solution Options

Traditional DB‑centric layering: organize logic around database tables.

DDD: treat the domain model as the core and decouple business logic from the schema.

DDD was chosen despite higher upfront learning cost because it yields business‑oriented modeling, clear boundaries, high cohesion, and low coupling.

Implementation Steps

Step 1 Modeling

Cross‑functional teams (product, development, testing) build a ubiquitous language and domain model using use‑case analysis (event → command → entity). Bounded contexts are identified and aggregates are defined. Detailed modeling steps are omitted for brevity.

Step 2 Architecture & Coding

The domain model guides an onion architecture enhanced with CQRS. The layers are:

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

Infrastructure Layer : Implements persistence gateways, handles DO‑PO conversion, and encapsulates data‑access details.

Application Layer : Depends on domain and infrastructure, provides query and command services, and uses a responsibility‑chain pattern for logic orchestration.

Interface Layer : Exposes HTTP/RPC endpoints to external callers.

Shared Package : Contains utilities, exception classes, and DTOs shared between domain and application layers.

Key architectural diagram (onion architecture with CQRS):

Onion architecture with CQRS
Onion architecture with CQRS

Practical Challenges & Solutions

Designing Aggregates for a Legacy System

Top‑down redesign conflicted with existing tables. The team first defined service boundaries, then designed aggregates that reuse existing tables where possible, simplifying entity‑DB mapping and preserving reusable code.

Thin Logic Orchestration

Application‑layer logic often mixes third‑party calls and messaging, leading to tangled code. A responsibility‑chain pattern modularizes each step (e.g., aggregate update, external call, message dispatch) into reusable nodes, keeping the command flow clear.

Decoupling Persistence from Aggregates

Each entity carries a status tag ( read, update, insert). A poWrapper holds the PO and status and provides a template method that executes the appropriate insert or update, eliminating scattered if‑else checks.

Persistence wrapper template
Persistence wrapper template

Simplifying CQRS Query Implementation

Traditional onion architecture prevents the application layer from accessing PO objects directly, causing extra DTOs. The team allowed the application layer to depend on the infrastructure layer, enabling direct PO usage and reducing conversion overhead, accepting tighter coupling to the current database schema.

Onion architecture dependency diagram
Onion architecture dependency diagram

Shared Dependency Package

The shared package houses common utilities, exception definitions, and DTOs needed across domain and application layers, easing code reuse during refactoring.

Shared dependency package diagram
Shared dependency package diagram

Conclusion

Domain‑Driven Design helps accurately represent business models and provides a robust architectural structure with highly cohesive aggregates, mitigating logic scattering and architectural chaos. However, DDD incurs significant learning and development costs, may not suit simple domains, and requires pragmatic compromises during implementation.

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.

backend architecturesoftware engineeringDomain-Driven DesignCQRSonion architectureLegacy Refactoring
NetEase Yanxuan Technology Product Team
Written by

NetEase Yanxuan Technology Product Team

The NetEase Yanxuan Technology Product Team shares practical tech insights for the e‑commerce ecosystem. This official channel periodically publishes technical articles, team events, recruitment information, and more.

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.