领域驱动设计(DDD)工程化实践:从MVC到DDD的代码重构

🌟 系列博客说明:

        本文是 DDD“理论→Demo→实战” 三部曲的Demo 落地篇基于 MVC 架构改造场景前文《领域驱动设计(DDD)全链路实践指南:从理论到落地的体系化方案》(理论核心篇)已搭建完整 DDD 知识框架;后文《智慧园区架构演进:DDD + 三大思想破解循环依赖实战》(实战落地篇)将进一步展示 DDD 在真实复杂项目中的应用,帮助读者从 “会用” 到 “活用”。

引言:‌

        在当今快速变化的业务环境中,传统的MVC架构逐渐暴露出业务逻辑分散、贫血模型、可测试性差等局限性。本文基于实际项目经验,系统阐述了如何将DDD从理论概念落地到工程实践,通过智慧园区系统案例,详细展示从战略设计到战术实现的完整流程。本文将揭示DDD不是架构师的专属工具,而是开发者回归业务本质的必备利器。

一、核心思想:面向开发者而非架构师‌

        DDD工程化的本质是‌让领域驱动设计回归到日常开发实践‌,而不是停留在理论层面。其核心包括:

1.1 从数据库思维到领域思维

        传统MVC架构以数据库表设计为出发点,导致业务逻辑分散在各个Service层。DDD工程化的本质是让开发回归业务本质,通过领域模型承载业务逻辑,实现技术架构与业务架构的统一。

1.2 业务边界优先原则

        DDD的核心价值在于‌以业务边界作为架构设计的第一原则‌,通过限界上下文明确划分不同业务域的职责。

二、MVC架构问题诊断

2.1 传统开发模式的局限性

传统开发模式‌呈现出明显的线性流程:基于数据库表设计 → 绘制UML图 → 业务逻辑组装

这种模式导致以下核心问题:

1. ‌业务逻辑分散在各个Service层

  • 核心业务流程被拆分到多个Service方法中
  • 缺乏统一的业务规则管理机制
  • 代码复用性差,相似逻辑在不同Service中重复实现

2. 实体对象成为贫血模型

  • 实体类仅包含getter/setter方法
  • 缺乏真正的业务行为和领域逻辑

2.2 贫血模型问题的深度解析

传统MVC架构中的实体对象仅仅是数据载体,缺乏业务行为,这一根本性缺陷导致:

2.2.1 业务规则验证分散
  • 参数校验逻辑分布在Controller层
  • 业务状态校验分布在Service层核心计算逻辑以私有方法形式隐藏在Service中
2.2.2 数据一致性难以保证
  • 缺乏明确的聚合边界
  • 事务管理复杂且容易出错
2.2.3 可测试性差
  • 单元测试需要启动整个Spring容器
  • 必须Mock大量依赖组件
  • 测试用例编写和维护成本高昂

三、DDD战略设计:划定业务边界

我们必须先进行战略设计,以识别和定义清晰的业务边界。战略设计方法详见博客《领域驱动设计(DDD)脉络与项目案例分析》第二章战略设计篇

3.1 识别核心子域

基于项目经验,智慧园区系统可识别为:

  • 园区资产管理子域‌(核心域)
  • 招商租赁子域‌(核心域)
  • 合同管理子域‌(支撑域)
  • 财务结算子域(支撑域)

3.2 定义限界上下文

空间管理上下文

  • 核心职责:园区空间资源状态管理
  • 领域概念:空间、空间状态、空间类型

3.3 上下文映射关系

  • 空间管理上下文 → 租赁交易上下文:通过"领域事件"异步协作
  • 统一通用语言:如"订单已确认"事件取代"修改订单状态"操作

四、DDD战术实现:代码改造实践

4.1 改造前:MVC架构代码

实体类(贫血模型)

@Data
public class ParkSpace {
    private Long id;
    private String spaceCode;
    private String spaceType;
    private BigDecimal area;
    private String status;
}

@Data  
public class LeasingOrder {
    private Long id;
    private Long spaceId;
    private String customerName;
    private String customerPhone;
    private LocalDate startDate;
    private LocalDate endDate;
    private BigDecimal totalRent;
    private String orderStatus;
    private Long contractDraftId;
}

Service层(上帝服务)

@Service
public class SpaceLeasingService {
    
    @Autowired
    private ParkSpaceMapper parkSpaceMapper;
    @Autowired
    private LeasingOrderMapper leasingOrderMapper;
    @Autowired
    private ContractService contractService;

    public LeasingOrder applyForLeasing(LeasingApplyRequest request) {
        // 参数验证
        if (request.getCustomerName() == null) {
            throw new IllegalArgumentException("客户姓名不能为空");
        }

        // 查询并校验空间状态
        ParkSpace targetSpace = parkSpaceMapper.selectById(request.getSpaceId());
        if (!"空闲".equals(targetSpace.getStatus())) {
            throw new IllegalStateException("该空间当前不可租赁");
        }

        // 创建租赁订单
        LeasingOrder newOrder = new LeasingOrder();
        newOrder.setSpaceId(request.getSpaceId());
        newOrder.setCustomerName(request.getCustomerName());
        newOrder.setCustomerPhone(request.getCustomerPhone());
        newOrder.setStartDate(request.getStartDate());
        newOrder.setEndDate(request.getEndDate());
        newOrder.setTotalRent(calculateTotalRent(targetSpace, request)));
        newOrder.setOrderStatus("待签约");
        leasingOrderMapper.insert(newOrder);

        // 更新空间状态
        targetSpace.setStatus("已预定");
        parkSpaceMapper.updateById(targetSpace);

        // 调用远程合同服务
        ContractDraftDTO contractDraft = contractService.generateContractDraft(
            new ContractDraftRequest(newOrder.getId(), request.getStartDate(), request.getEndDate())));
        newOrder.setContractDraftId(contractDraft.getId());

        return newOrder;
    }
}

4.2 改造过程:DDD重构

4.2.1 聚合根设计
public class Space implements AggregateRoot {
    private SpaceId id;
    private SpaceCode code;
    private SpaceType type;
    private Area area;
    private SpaceStatus status;

    public LeasingOrder applyForLeasing(CustomerInfo customer, LeaseTerm leaseTerm, Rental rental){


        if (!this.status.isAvailable()) {
            throw new IllegalStateException("该空间当前不可租赁");
        }

        LeasingOrder newOrder = LeasingOrder.create(this.id, customer, leaseTerm, rental);
        this.status = SpaceStatus.RESERVED;
        this.registerDomainEvent(new SpaceReservedEvent(this.id, newOrder.getId()));
        return newOrder;
    }
}
4.2.2 值对象封装
public class CustomerInfo implements ValueObject {
    private final String name;
    private final String phone;

    public CustomerInfo(String name, String phone) {
        if (name == null || name.trim().isEmpty()) {
            throw new IllegalArgumentException("客户姓名不能为空");
        }
        this.name = name.trim();
        this.phone = phone.trim();
    }
}

4.3 改造后:DDD架构代码

4.3.1 充血的领域模型
public class LeasingOrder implements AggregateRoot {
    private LeasingOrderId id;
    private SpaceId spaceId;
    private CustomerInfo customer;
    private LeaseTerm leaseTerm;
    private Rental rental;
    private OrderStatus status;

    public static LeasingOrder create(SpaceId spaceId, CustomerInfo customer, LeaseTerm leaseTerm, Rental rental){
        if (customer == null) {
            throw new IllegalArgumentException("客户信息不能为空");
        }

        LeasingOrder order = new LeasingOrder();
        order.id = LeasingOrderId.nextId();
        order.spaceId = spaceId;
        order.customer = customer;
        order.leaseTerm = leaseTerm;
        order.rental = rental;
        order.status = OrderStatus.PENDING_SIGN;
        order.registerDomainEvent(new LeasingOrderCreatedEvent(order.id));
        return order;
    }
}
4.3.2 应用服务层
@Service
@Transactional
public class SpaceLeasingApplicationService {

    @Autowired
    private SpaceRepository spaceRepository;
    @Autowired
    private LeasingOrderRepository leasingOrderRepository;

    public void applyForLeasing(ApplyForLeasingCommand command) {
        Space space = spaceRepository.findById(command.getSpaceId()))
                .orElseThrow(() -> new SpaceNotFoundException("空间不存在")));

        LeasingOrder newOrder = space.applyForLeasing(command.getCustomer(), command.getLeaseTerm(), command.getRental()));

        leasingOrderRepository.save(newOrder);
        spaceRepository.save(space);
    }
}

4.4 改造总结:DDD 四层架构落地全景

        通过 MVC 到 DDD 的代码重构,最终形成 “接口层→应用层→领域层(核心)→基础设施层” 的四层架构。各层职责清晰、边界明确,既保障了业务逻辑的内聚,又实现了技术细节的隔离,具体架构分工如下:

架构分层

核心职责

本项目落地示例

设计原则

接口层

暴露外部访问入口,适配请求 / 响应格式,承接外部交互

控制器(如 SpaceLeasingController,接收前端请求)、DTO 转换、参数校验器

面向外部访问,仅做 “适配转换”,不处理任何业务逻辑

应用层

协调用例流程、组装领域能力、管理事务边界,不包含核心业务规则

应用服务(SpaceLeasingApplicationService)、命令对象(ApplyForLeasingCommand)

轻量级协调,仅负责 “接收接口层请求→调用领域层→触发持久化”,不侵入核心业务逻辑

领域层(核心)

封装核心业务逻辑、业务规则、领域模型,定义抽象依赖接口(如仓储接口)

聚合根(Space、LeasingOrder)、值对象(CustomerInfo、LeaseTerm)、领域事件(SpaceReservedEvent)、仓储接口(SpaceRepositoryInterface)

纯业务聚焦,不依赖任何技术框架 / 其他层,是整个架构的稳定核心

基础设施层

实现领域层抽象接口、提供技术实现细节,适配外部依赖

仓储实现(SpaceRepositoryImpl、LeasingOrderRepositoryImpl)、RPC 客户端(ContractService 适配器)

依赖倒置,通过接口适配领域层需求,隔离数据库、外部服务等技术细节,为其他层提供底层支撑

架构核心优势总结

  1. 职责内聚:业务规则集中在领域层,技术实现隔离在基础设施层,修改互不影响。
  2. 可测试性:领域层可脱离 Spring 容器独立测试,无需 Mock 大量依赖。
  3. 扩展性:新增业务功能时,只需扩展领域模型或应用服务,基础设施层适配即可(如新增 “续租” 功能,仅需在 LeasingOrder 聚合根新增 renew () 方法)。
  4. 微服务适配:基于限界上下文划分的四层架构,可直接通过添加注解(如 @DubboService、@RestController)暴露为微服务,无需重构核心逻辑(第六章微服务升级路径)。

五、架构演进效果验证

5.1 功能一致性对比

功能要点

MVC实现

DDD实现

一致性

客户信息验证

Service方法中if语句

Command构造函数验证

完全一致

空间状态验证

if (!"空闲".equals(status))

if (!this.status.isAvailable()))

逻辑等价

订单创建

new LeasingOrder()

LeasingOrder.create()

结果相同

合同生成

直接RPC调用

领域事件驱动

最终状态一致

5.2 可测试性提升

MVC测试复杂度高

@SpringBootTest
class SpaceLeasingServiceTest {
    @MockBean
    private ParkSpaceMapper parkSpaceMapper;
    @MockBean
    private ContractService contractService;
}

DDD测试简单直接

class SpaceTest {
    @Test
    void testApplyForLeasing_Success() {
        Space space = new Space(...);
        space.setStatus(SpaceStatus.AVAILABLE);

        LeasingOrder order = space.applyForLeasing(customer, leaseTerm, rental);

        assertThat(space.getStatus()).isEqualTo(SpaceStatus.RESERVED);
    }
}

六、微服务升级路径

6.1 平滑升级策略

基于清晰的限界上下文划分,微服务拆分变得异常简单。只需在原有的应用服务上添加服务暴露注解即可完成微服务化改造。

服务暴露实现:

// 在应用服务上简单添加注解,即可暴露为微服务接口
@Service
@DubboService // 或者 @RestController
@Transactional
public class SpaceLeasingApplicationService {
    // 内部实现与DDD阶段完全一致,无需改动!
}

6.2 事件驱动架构实现

6.2.1 事件发布机制

通过领域事件实现服务间解耦,具体流程如下:

  1. 订单服务‌发布SpaceReservedEvent领域事件
  2. 合同服务‌监听该事件并自动生成合同草稿
  3. 库存服务‌监听事件进行库存预占
  4. 通知服务‌监听事件发送确认消息

6.2.2 事件处理流程

// 在合同服务中,监听“空间租赁订单创建”事件
@Component
public class ContractEventHandler {
    
    @DubboReference
    private ContractApplicationService contractAppService;

    @EventListener
    public void handleLeasingOrderCreated(LeasingOrderCreatedEvent event) {
        // 接收到事件后,在合同服务的上下文内生成合同
        contractAppService.generateContractForOrder(event.getOrderId());
    }
}

6.3 技术架构升级

6.3.1 服务治理组件

  • 服务注册与发现:Nacos / Eureka
  • 配置中心:Apollo
  • 服务网关:Spring Cloud Gateway
  • 链路追踪:SkyWalking

6.3.2 数据一致性保障

  • 基于Saga模式的分布式事务管理
  • 最终一致性保证机制
  • 补偿事务处理逻辑

6.4 部署架构优化

6.4.1 容器化部署

  • Docker容器封装
  • Kubernetes集群管理
  • 持续集成/持续部署流水线

DDD的适用边界与工程化建议

7.1 DDD的适用场景

  1. 业务逻辑复杂且频繁变化‌:系统包含大量的业务规则、状态流转和校验逻辑,如易达慧云项目‌,涉及招商、租赁、合同、财务等多个复杂业务域。
  2. 需要长期演进‌:业务会不断发展变化,系统需要长期维护。
  3. 团队规模较大‌:需要清晰的模块边界来规范多个团队或开发者之间的协作。

7.2 DDD的陷阱与不适用场景

  1. 简单CRUD系统‌:对于仅涉及增删改查、几乎没有业务规则的系统,使用DDD会导致"类爆炸",开发效率极低,属于典型的过度设计。(如报表系统)
  2. 业务边界清晰且稳定的系统

7.3 工程化最佳实践

  1. 渐进式重构‌:选择核心、复杂且频繁变动的子域作为试点,不要试图一次性将整个系统DDD化。
  2. 团队共识建设‌:确保整个团队对DDD的基本理念和项目的通用语言有共同的理解。
  3. 工具链支持‌:建立代码生成、测试框架等配套工具
  4. 避免教条主义‌:不要为了追求"完美的DDD"而过度设计。DDD是工具,不是目的。

结语

        有了限界上下文的划分,‌单体、微服务、事件驱动‌等架构仅是领域之间‌不同的协作方式‌,而领域本身需保持稳定。

        通过这个功能要点的改造案例,我们可以清晰地看到DDD工程化实践的核心价值:DDD的真正力量不在于创造新的功能,而在于以一种更清晰、更健壮、更易于演进的方式来实现这些功能。

        它通过‌限界上下文‌为我们提供了划分微服务边界的科学依据,通过‌聚合根‌保证了业务规则的一致性,通过‌领域事件‌实现了服务间的解耦。DDD提供的这套结构化思维方法和建模工具,将成为架构决策的可靠指南。


    📚 我的技术博客导航:[点击进入一站式查看所有干货]


    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包

    打赏作者

    递归尽头是星辰

    你的鼓励将是我创作的最大动力

    ¥1 ¥2 ¥4 ¥6 ¥10 ¥20
    扫码支付:¥1
    获取中
    扫码支付

    您的余额不足,请更换扫码支付或充值

    打赏作者

    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值