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的实现难度。

优点
  1. 高内聚、低耦合:通过聚合(Aggregate)和领域事件(Domain Event)等机制,确保业务模型的高内聚性和系统的低耦合性。
  2. 模型与数据分离:通过依赖倒置原则(模型与数据分离),聚合负责业务逻辑,仓储接口实现在基础设施层,便于进行单元测试。
  3. 统一语言促进协作:建立领域统一语言,便于多团队协作,减少沟通成本。
  4. 丰富的技术支持: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通常采用四层架构设计,每一层都有明确的职责:

  1. 用户接口层(Interface Layer):负责接收用户请求,调用应用层服务,并返回响应结果。
  2. 应用层(Application Layer):协调领域层和基础设施层,实现用例(Use Case),不包含业务逻辑。
  3. 领域层(Domain Layer):包含核心业务逻辑,是系统的灵魂所在,包括实体、值对象、聚合、领域服务和领域事件等。
  4. 基础设施层(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则能提供更好的解决方案。无论选择哪种架构,都应该以业务为核心,以解决问题为导向,不断优化和改进系统设计。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐