How to Seamlessly Refactor MVC Projects into DDD Architecture

This article explains why legacy MVC codebases become tangled, introduces the domain‑driven design (DDD) layered architecture, and provides a step‑by‑step, low‑cost migration path—including layer mapping, call‑chain illustration, common pitfalls, and a concrete refactoring example with design patterns and code snippets.

dbaplus Community
dbaplus Community
dbaplus Community
How to Seamlessly Refactor MVC Projects into DDD Architecture

Problem Overview

Legacy MVC projects suffer from severe architectural decay because objects, services, and components are cross‑used without clear boundaries, making long‑term iteration costly.

Why MVC Decays

MVC separates state (PO/VO/enum) from behavior (service), which speeds early delivery but over time leads to services calling each other and sharing domain objects, creating a tangled “big wardrobe” of code.

DDD Layered Architecture

Domain‑Driven Design (DDD) treats each bounded context as an independent “person” that owns its models, services, repositories, etc., resulting in clearer boundaries and lower maintenance cost.

A typical four‑layer DDD structure includes:

app : application startup, AOP, configuration, image building.

api : external RPC interface definitions.

domain : core business logic, aggregates, entities, value objects, repositories, events, services.

infrastructure : persistence, Redis, config center, event messaging, external service adapters.

gateway : external HTTP/RPC call encapsulation.

types : common DTOs, enums, response objects.

Migration from MVC to DDD

Key steps:

Move domain‑specific logic from service to the domain layer, creating dedicated sub‑packages (e.g., xxx, yyy, zzz).

Shift generic utilities such as Redis and configuration from service to the dao / infrastructure layer, applying dependency inversion.

Treat the original service as the application/case layer that orchestrates domain services.

Keep the export (RPC) layer unchanged unless the domain package is exposed, in which case move it to the export layer.

The mapping diagram shows MVC layers on the left and DDD layers on the right, using the same colour coding to illustrate correspondence.

Layered Call Chain

The DDD call chain from an interface implementation to each module is visualised, helping developers decide where to place specific functionality.

Common Pitfalls

Simply switching the skeleton from MVC to DDD does not automatically produce clean code; existing “old furniture” must be refactored, and appropriate design patterns should be applied.

Refactoring Example with Design Patterns

A credit‑limit‑adjustment scenario demonstrates the use of an abstract class as a template, strategy and factory patterns for rule validation, and dependency inversion for infrastructure services.

public AdjustAssetOrderEntity acceptAdjustAssetApply(AdjustAssetApplyEntity adjustAssetApplyEntity) {
    // 1. 参数校验
    this.parameterVerification(adjustAssetApplyEntity);
    // 2. 查询申请单数据,如已经存在则直接返回
    AdjustAssetOrderEntity orderEntity = queryAssetLog(adjustAssetApplyEntity.getPin(), adjustAssetApplyEntity.getAccountType(), adjustAssetApplyEntity.getTaskNo(), adjustAssetApplyEntity.getAdjustType());
    if (null != orderEntity) {
        log.info("pin={} taskNo={} 受理申请,检索到任务存在进行中的申请单。", adjustAssetApplyEntity.getUserId(), adjustAssetApplyEntity.getTaskNo());
        return orderEntity;
    }
    // 3. 以下流程放到分布式锁内处理【避免相同请求二次进入】
    String lockId = genLockId(adjustAssetApplyEntity.getAdjustType(), adjustAssetApplyEntity.getUserId());
    try {
        // 3.1 分布式锁:加锁
        long state = lock(lockId);
        if (0 == state) {
            throw new AccountRuntimeException(BizResultCodeEm.DISTRIBUTED_LOCK_EXCEPTION.getCode(), "分布式锁异常,当前用户行为处理中。");
        }
        // 3.2 账户查询
        UserAccountInfoDTO userAccountInfoDTO = queryJtAccount(adjustAssetApplyEntity.getUserId(), adjustAssetApplyEntity.getAccountType());
        // 3.3 基础校验;(1)账户类型、(2)状态状态、(3)额度类型、(4)账户逾期、(5)费率类型【暂无】
        LogicCheckResultEntity logicCheckResultEntity = doCheckLogic(adjustAssetApplyEntity, userAccountInfoDTO,
                DefaultLogicFactory.LogicModel.ACCOUNT_TYPE_FILTER.getCode(),
                DefaultLogicFactory.LogicModel.ACCOUNT_STATUS_FILTER.getCode(),
                DefaultLogicFactory.LogicModel.ACCOUNT_QUOTA_FILTER.getCode(),
                DefaultLogicFactory.LogicModel.ACCOUNT_OVERDUE_FILTER.getCode()
        );
        if (!AssetCycleQuotaAlterCodeEnum.E0000.getCode().equals(logicCheckResultEntity.getCode())) {
            log.info("userId={} taskNo={} 规则校验过滤拦截。code:{} info:{}", adjustAssetApplyEntity.getUserId(), adjustAssetApplyEntity.getTaskNo(), logicCheckResultEntity.getCode(), logicCheckResultEntity.getInfo());
            throw new AccountRuntimeException(logicCheckResultEntity.getCode(), logicCheckResultEntity.getInfo());
        }
        // 3.4 受理调额
        return this.acceptAsset(adjustAssetApplyEntity, userAccountInfoDTO);
    } finally {
        // 3.1 分布式锁:解锁
        this.unlock(lockId);
    }
}

The refactored code is clearer: it validates parameters, checks existing orders, acquires a distributed lock, performs business rule checks via a strategy/factory, and finally processes the adjustment.

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.

Design PatternsMVCDDDrefactoring
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.