DDD与MVC设计架构的深度对比:从传统分层到领域驱动的演进之路
MVC架构:传统而经典的三层设计模式
MVC(Model-View-Controller)作为软件开发中最为经典和广泛采用的设计架构,采用经典的三层架构思想,将系统清晰地划分为Controller、Service和Model三个层次,各司其职,协同工作。
MVC架构的核心组件
-
Controller(控制器层):作为系统的接口层,对外暴露API接口,负责接收和解析用户请求,协调Service层完成业务处理,并返回响应结果。可以将其理解为系统的"外交官",负责与外部世界的沟通。
-
View(视图层):直接面向用户的界面展示层,负责将处理结果以友好的方式呈现给用户。它是系统的"脸面",决定了用户与系统交互的直观体验。
-
Model(模型层):业务模型层,包含实体类(Entity)和数据访问对象(DAO)。实体类作为数据对象(DO),封装了业务领域的核心数据;DAO则负责与数据库交互,完成数据的持久化操作。模型层是系统的"核心",承载着业务逻辑的基础数据结构。
深入理解MVC架构:接口层(Controller)定义了系统能提供的能力,就像一个人的证书(如英语四级、计算机二级)只能证明他具备某种能力,但无法展示具体如何运用这种能力。业务逻辑的实现则体现在Service层,就像每个人处理问题的方式各不相同。Model层则是业务领域中概念的抽象,正如现实世界中需要人去执行任务,在程序中则通过模型之间的配合来完成各种业务流程。
MVC架构的优缺点分析
优点
- 清晰的职责分离:将程序划分为三个层次,每层专注于自己的职责,降低了系统的复杂性。
- 开发效率高:结构简单明了,易于理解和实现,适合快速开发。
- 团队协作友好:明确的接口定义便于团队成员并行开发。
缺点
- Service层职责过重:需要完成参数校验、业务逻辑处理、调用中间件(基础设施)等多项任务,导致代码臃肿,难以进行单元测试。
- 实体模型内聚性不足:实体仅包含属性,业务方法分散在Service层,不利于维护和保持业务模型的高内聚性。
- 业务逻辑与数据持久化耦合:业务逻辑与数据访问逻辑混合在一起,增加了系统的复杂度和维护成本。
DDD(领域驱动设计):以业务为核心的架构思想
程序设计本质上是一个解决问题的过程,而解决问题的核心在于准确理解问题本身。DDD(Domain-Driven Design,领域驱动设计)正是以业务领域(业务需求)为核心,指导系统设计的一种架构思想。
DDD的优缺点分析
缺点
- 实现复杂度高:除了需要实现领域事件、领域服务等核心概念外,还涉及converter(DO与PO的转换)和assembler(DTO与DO的转换)等辅助组件,增加了开发复杂度。
- 学习曲线陡峭:需要深入理解业务领域,建立统一语言,对开发团队的业务理解能力要求较高。
- 开发周期可能延长:前期需要投入大量时间进行领域建模和概念设计。
应对DDD复杂性的策略:随着AI技术的发展,许多DDD实现中的复杂工作可以通过AI工具辅助完成,如自动生成转换代码、提供领域建模建议等,这大大降低了DDD的实现难度。
优点
- 高内聚、低耦合:通过聚合(Aggregate)和领域事件(Domain Event)等机制,确保业务模型的高内聚性和系统的低耦合性。
- 模型与数据分离:通过依赖倒置原则(模型与数据分离),聚合负责业务逻辑,仓储接口实现在基础设施层,便于进行单元测试。
- 统一语言促进协作:建立领域统一语言,便于多团队协作,减少沟通成本。
- 丰富的技术支持:Spring Events、JPA等框架提供了对DDD的支持,如ApplicationPublisher、JPA的AbstractAggregateRoot等;此外,还有成熟的企业级框架如Axon Framework专门支持DDD实现。
DDD核心概念详解
实体(Entity)
- 特征:拥有唯一标识号、完整的生命周期,包含属性和业务逻辑方法,具备自己的校验能力。
- 示例:以人为例,每个人都有唯一的身份证号,经历出生、成长、死亡等阶段,在这个过程中身高体重会不断变化,但对象仍然是同一个人。
值对象(Value Object)
- 特征:仅包含属性,属性一旦修改就成为不同的对象,因此其属性通常设计为不可变。
- 示例:地址对象,广西柳州和广西桂林是不同的地址对象,任何一个属性的变更都会创建新的地址对象。
对象的概念:对象是指客观世界的事物,可以是抽象的也可以是具体的,如眼前的手机、电脑等。在DDD中,我们通常从概念和共识的角度来区分不同类型的对象。
聚合(Aggregate)
- 定义:由多个实体和值对象组成的整体,由该整体负责维护数据完整性和一致性。
- 作用:确保聚合内部的业务规则和数据一致性,同时限制外部对聚合内部数据的直接访问。
聚合根(Aggregate Root)
- 定义:包含一个或多个实体和值对象的特殊实体,作为聚合的入口点,用于封装聚合内部的所有对象。
- 示例:以手机为例,手机由多个部件(领域模型)组成,但只能通过屏幕(聚合根)进行操作,这体现了高内聚和低耦合的设计原则。
聚合与聚合根的关系:以入库单为例,入库单聚合包含入库单项实体,入库单项又包含库位号、货物条码等值对象,形成了一个嵌套的业务结构。入库单作为聚合根,统一管理整个聚合的数据和操作。
为什么需要区分实体、值对象和聚合根?
这些概念实际上是对业务领域模型的抽象,帮助我们更好地理解和表达业务规则。通过合理划分这些概念,我们可以构建出更加清晰、可维护的业务模型。
业务概念模型的通俗理解:业务概念模型听起来可能有些抽象,通俗地讲,就像完成一项工作需要很多人参与,我们需要在业务领域中识别出这些"参与者",并通过指挥(调用)它们进行交互配合来完成工作。需要完成的任务就是业务流程,而如何完成任务则是业务规则。这三者是程序组成的三大要素。
事件风暴(Event Storming)
DDD中的业务流程和业务规则主要通过领域服务和领域事件来实现。
领域服务(Domain Service)
- 职责:编排聚合根(业务逻辑)、仓储接口(持久化)等组件,共同完成业务流程。
- 特点:当业务逻辑不适合放在实体或值对象中时,可以使用领域服务来封装这些跨聚合的业务逻辑。
领域事件(Domain Event)
- 职责:对业务规则进行控制和表达,记录业务领域中的重要事件。
- 工作机制:领域事件注重已经发生的事实,聚合根的业务方法通常没有返回值,而是通过领域事件来通知后续处理步骤。例如,完成自己的工作后通知下一个人,下一个人收到通知后再进行处理。
事件处理机制
- 生产者:发布领域事件的组件,通常是聚合根或领域服务。
- 事件总线:负责事件的发布和订阅,实现组件间的松耦合通信。
- 消费者:订阅并处理领域事件的组件,根据事件内容执行相应的业务逻辑。
DDD的四层架构
DDD通常采用四层架构设计,每一层都有明确的职责:
- 用户接口层(Interface Layer):负责接收用户请求,调用应用层服务,并返回响应结果。
- 应用层(Application Layer):协调领域层和基础设施层,实现用例(Use Case),不包含业务逻辑。
- 领域层(Domain Layer):包含核心业务逻辑,是系统的灵魂所在,包括实体、值对象、聚合、领域服务和领域事件等。
- 基础设施层(Infrastructure Layer):提供技术支持,如数据库访问、消息队列、外部API调用等,实现领域层定义的仓储接口等。
CQRS(命令查询职责分离)
DDD非常适合实现CQRS(Command Query Responsibility Segregation,命令查询职责分离)模式,该模式将系统的读写操作分离,针对不同的操作采用不同的数据模型和存储策略。
- 适用场景:对于读多写少、读写性能要求差异较大的系统,CQRS可以显著提高系统性能和可扩展性。
- 实施建议:考虑到CQRS的实现复杂度,建议从简单的项目开始,逐步深入实现。
DDD如何解决MVC架构的问题
解决业务逻辑分散问题
DDD采用充血模型(Rich Domain Model),实体不仅包含属性,还包含业务逻辑方法,使业务逻辑更加内聚,便于维护和理解。
解决Service层代码臃肿问题
通过引入基础设施层,将数据持久化工作移至基础设施层完成,同时将配置基础设施(如网关、中间件等)的工作也放在这一层,使Service层更加专注于业务逻辑。
为什么要在基础设施层完成数据持久化?
实现模型与数据分离,便于进行单元测试。就像在草稿纸上可以随意修改,但在答题卡上就不能随意涂改一样,模型设计应该与数据持久化分开考虑。
如何实现模型与数据分离?
在领域层定义仓储接口,基础设施层实现这些仓储接口,通过依赖注入将实现类注入到领域层使用。
仓储接口与Mybatis的mapper有什么关系?
仓储接口是抽象的,定义了数据访问的契约;而mapper则是具体的实现,负责与数据库交互。仓储接口实现类会调用converter和mapper完成对数据的持久化操作。
DDD中的实体和值对象如何与数据库表对应?
在基础设施层创建PO类(Persistence Object)和Converter包,Converter负责将DO(Domain Object,领域对象)转换成PO(持久化对象)。可以使用MapStruct框架简化这一转换过程。
DO与PO的区别:DO包含值对象和实体,通常是嵌套的;而PO是存储在数据库中的,是扁平化的结构。以入库单为例:
// 入库单聚合根
public class InboundBill {
// 入库单号,业务id
private InboundBillNo billNo;
private List<InboundBillItem> items;
private AtomicInteger nextLineNumber = new AtomicInteger(1);
}
// 入库单详情实体
public class InboundBillItem {
// 采用入库单号和业务行号作为组合id
private InboundBillNo billNo;
private AtomicInteger lineNumber;
private Quantity plannedQuantity;
private Quantity actuallyQuantity;
}
// 值对象
public class Quantity {
private final BigDecimal value;
// 计量单位枚举类
private final UnitOfMeasure unit;
}
而在数据库表中,会有入库单和入库单项两张表,其中quantity值对象的unit单位作为入库单项表的一个字段,plannedQuantity和actuallyQuantity的value作为另外两个字段。通常不会将quantity属性的所有字段都作为独立字段存储。
总结与展望
MVC架构作为传统而经典的设计模式,以其简单明了的结构和易于理解的特点,在许多中小型项目中仍然发挥着重要作用。然而,随着业务复杂度的增加,MVC架构的局限性也逐渐显现,特别是在业务逻辑分散、Service层职责过重等方面。
DDD作为一种更加面向业务、更加灵活的架构思想,通过聚合、领域事件、领域服务等概念,有效地解决了MVC架构中的许多问题。虽然DDD的实现复杂度较高,学习曲线较陡,但其带来的高内聚、低耦合、模型与数据分离等优势,使其在复杂业务系统中展现出强大的生命力。
随着AI技术的发展,许多DDD实现中的复杂工作可以通过AI工具辅助完成,这大大降低了DDD的实现门槛。同时,Spring、JPA等框架对DDD的支持也越来越完善,为DDD的落地提供了有力的技术保障。
在实际项目中,我们应该根据业务复杂度、团队规模和项目需求,选择合适的架构模式。对于简单项目,MVC可能更加合适;而对于复杂业务系统,DDD则能提供更好的解决方案。无论选择哪种架构,都应该以业务为核心,以解决问题为导向,不断优化和改进系统设计。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)