在日常开发中,我们之前工作中经常接手的大多数都是传统 MVC 架构体系的项目。然而,随着现在分布式和微服务架构的普及,越来越多的项目开始重构、拆分,传统的 MVC 架构也逐渐向 DDD 架构演进。为什么需要将传统架构重构为 DDD 架构?MVC 架构相比如今备受关注的 DDD 架构又有哪些不足?本文将探讨 MVC 与 DDD 的核心区别,分析传统架构在现代复杂业务场景中的挑战,以及 DDD 是如何解决这些问题的。
在讨论 DDD 和 MVC 之前,我们需要先了解我们项目业务架构演变的过程。从最初的最简单的单体架构到后面的集群架构再到今天最流行的分布式、微服务架构。这种架构发展过程体现了技术的飞快发展,也反映了我们在开发项目过程中在应对复杂业务需求和快速增长的用户量时,对架构设计的更高要求。
在项目开发的早期阶段,当业务量很小、需求简单时,常用的一种架构就是“一层架构”。这种架构的特点是所有代码逻辑、配置、视图和功能都写在一个文件中。这种方式在项目初期能够快速实现功能,便于开发者集中处理各个逻辑。
在这个结构中,所有逻辑集中在一个文件里,开发速度快,适合小型项目。业务逻辑、数据处理和页面展示都在一个地方,开发者可以快速实现和调整功能。
随着项目的发展,业务量逐渐增加,代码和功能的复杂度也随之提升。这时,一层架构开始暴露出一些问题:
因此,当业务需求增长时,就需要更合理的架构设计来应对复杂性,以便更好地组织代码、分离关注点并提高项目的可维护性。
在早期的小型项目中,由于代码量和业务逻辑较为简单,通常所有代码都集中在一个文件中,以一层架构的方式快速完成需求。然而,随着项目扩展和复杂度提升,一层架构逐渐暴露出难以维护和扩展的缺陷,无法满足复杂业务需求。这时,我们需要一种更合理的分层方式来组织代码,于是 MVC 架构应运而生。
随着业务复杂度的增加,MVC 架构提供了一种更清晰的代码组织方式。它通过将代码划分为 Model(模型层) 、View(视图层) 和 Controller(控制层) 三部分(有些也会有个Services 逻辑层部分),实现了关注点分离,增强了代码的可维护性和扩展性。
MVC 架构通过合理的分层方式,将数据、界面和控制逻辑解耦,为项目的扩展和维护打下了良好的基础。
通过分层架构设计,特别是 MVC 架构,我们能够在代码组织和项目维护上获得以下优点:
虽然分层架构带来了组织上的清晰和职责分离,但其本质上还是运行在单台服务器上的单体架构。
在上面的内容中,我们讨论了一层架构和 MVC 架构的设计,这些都是属于 单体架构 的范畴,通常是运行在单机环境下的单机架构。尽管通过 MVC 架构的分层设计,我们获得了更清晰的代码组织和职责分离,但它仍然是单机架构,所有功能都集中在同一个应用实例中运行,依赖单台服务器的性能和资源。
随着业务量的增长和用户量的增加,单体架构逐渐暴露出以下问题:
随着互联网业务量的快速增长,单机架构逐渐无法满足业务需求,我们面临着常说的三高问题:
高并发是指系统能够同时处理大量的请求,承受大量用户同时在线的压力,并在负载增大时仍能快速响应,避免因请求过多导致系统崩溃。高并发的最典型特点就是流量大,例如秒杀活动、电商促销、抢票系统等场景,往往会出现极高的并发需求。
在技术上,高并发的能力通常通过以下几个核心指标来衡量:
这些指标综合反映了系统的处理能力。当系统的 QPS 较高、RT 较低时,说明系统能够在短时间内响应大量请求,具备较好的并发处理能力。
单机架构中,由于所有功能模块都集中在同一台服务器上运行,系统的并发能力受到单机硬件性能的限制,包括 CPU、内存、网络带宽、磁盘 I/O 等。即使在硬件资源较高的情况下,单机架构也存在以下瓶颈:
在单机架构中,由于所有请求都在单台服务器上处理,系统的并发能力受到单机硬件性能的限制。单机的 CPU、内存、网络带宽等资源有限,导致单机难以承受大量并发请求。尽管可以通过升级硬件来提升性能,但单台机器的硬件资源总有上限,无法无限扩展。
高可用是指系统能够在出现故障时,迅速恢复并持续提供服务,确保系统的稳定性和连续性。高可用性在现代互联网应用中至关重要,尤其是在用户依赖度较高的系统中(如支付系统、医疗系统等),即便发生故障也要保证服务不中断。
高可用性通常通过以下几个核心指标来衡量:
高可用的系统通常追求 高可用性比例(如 “5 个 9” - 99.999%),意味着全年仅 5 分钟左右的停机时间。
高可用系统的目标是最大化系统的可用性比例,通过高效的容错和恢复机制减少故障影响。高可用系统需要具备高度的容错性和恢复性,在单机架构中,系统的高可用性较难保障,因为一旦这台服务器出现故障,所有服务都会中断。
高性能指的是系统能够快速处理请求,在最短的时间内完成数据处理并反馈给用户。高性能不仅要求系统能够处理大量请求,还要求系统在响应每个请求时具备较低的延迟,提供流畅的用户体验。
高性能的系统通常会使用优化的代码、合理的数据结构、缓存等技术,确保系统可以在最短时间内完成请求处理。高性能的衡量指标主要包括:
高性能的场景示例
在单机架构中,系统的性能直接受限于单台服务器的硬件资源(CPU、内存、磁盘、网络等)。即便系统通过代码优化和缓存提高了处理速度,单台服务器的硬件能力始终有限,单机性能存在瓶颈,主要体现在以下方面:
因此,单机架构在性能提升上有明显的局限性,即使通过硬件升级也无法无限提升。当业务需求超过单机处理能力时,我们需要通过分布式架构和集群来提升系统的整体性能。
集群架构是通过部署多台相同的服务器实例来协同工作,以应对系统负载,提升整体的性能、并发能力和可用性。集群中的每个服务器实例称为一个节点,所有节点一起提供服务。用户的请求会被分发到不同的节点上,分散单台服务器的压力。这种方式不仅提升了系统的并发处理能力,也增强了系统的容错和恢复能力,适应了三高问题的需求。
集群架构是相对简单而有效的扩展方法,尤其适用于单体应用的扩展。在集群架构中,常用负载均衡器(如 Nginx、HAProxy)将请求分配到不同的服务器实例上,确保请求得到快速响应和有效处理。
通过集群架构,系统能够更好地应对高并发、高可用和高性能的需求。
在集群架构中,高并发是通过水平扩展来实现的,即增加更多的服务器实例来分担请求。用户请求被负载均衡器分发到多台服务器,减少单个节点的压力,从而提升整体并发能力。
集群架构的高可用特性体现在多个实例的容错性上。当某台服务器出现故障时,负载均衡器会自动将流量转移到其他正常运行的服务器上,确保服务不受影响。这种冗余设计提高了系统的容错和恢复能力。
集群架构能够提升系统的高性能,因为多个节点可以共同分担负载,加快请求处理速度。每个节点负责部分请求,分担了计算和 I/O 操作的压力,使得系统能够在高负载下仍保持快速响应。
在单体架构下,数据库通常是单实例部署。随着数据量和访问量的增长,单实例数据库逐渐暴露出性能和可用性的问题。例如,频繁的读写操作会造成数据库负载过高,导致响应变慢,甚至出现崩溃风险。
为了提高 MySQL 的性能和可用性,常用的方案包括主从复制、读写分离和分库分表。
主从复制
主从复制是一种常见的数据库容灾和扩展手段,通过配置一台主数据库和多台从数据库,主库负责写操作,从库负责读操作。当主库写入数据后,数据会自动同步到从库。
读写分离
在主从复制的基础上,实现读写分离。主库专门用于写操作,从库专门用于读操作。应用程序通过中间层(如数据库代理)自动将读请求分发到从库,将写请求发送到主库。
分库分表
随着数据量的进一步增加,主从复制和读写分离可能仍然无法满足需求,这时可以考虑对数据库进行分库分表。分库分表将数据按某种规则分散到不同的库和表中,减少单个表的负担。
在单体架构中,Redis 通常被用作单实例缓存。当请求量增加或数据量增大时,单实例缓存可能会遇到性能瓶颈,无法有效支撑高并发场景。
为了提升 Redis 缓存系统的高可用性和并发处理能力,常用的方案包括主从复制、Redis 集群和哨兵机制。
主从复制
Redis 主从复制配置一台主节点和多台从节点,主节点负责写操作,从节点负责读操作。当主节点写入数据时,会自动同步到从节点。
Redis 集群
Redis 集群是一种分布式缓存架构,将数据分散存储在多个节点上,每个节点只负责部分数据。Redis 集群使用一致性哈希分片,将请求分配到不同的节点上处理。
哨兵机制
Redis 哨兵机制用于监控主节点的健康状态,当主节点故障时,哨兵可以自动切换到从节点,确保缓存服务的高可用性。
通过主从复制、集群和哨兵等架构设计,MySQL 和 Redis 能够更好地支持高并发和高可用需求,为系统提供稳定、高效的数据支持。这些架构模式通常和集群架构一起使用,使数据库和缓存系统的性能、可用性得到显著提升。
集群架构通过部署多个相同的应用实例来分担请求负载,解决了单机架构在高并发、高可用、高性能方面的瓶颈,带来了显著的好处:
然而,尽管集群架构在三高方面带来了显著的提升,它仍然具有以下局限性:
因此,集群架构在应对初期的高并发和高可用需求时效果显著,但随着业务需求的进一步复杂化,尤其是功能模块增多时,我们的业务代码会变得更加庞大臃肿 耦合度也更加的高。所以我们要针对我们的业务代码进行重构拆分。这里的重构指的是我们通过将我们的代码业务系统拆分为独立的服务单元,为后面业务提供了更灵活、扩展性更强的解决方案。所以这种重构设计理念就逐渐演变成我们现在所流行分布式与微服务架构思想。
分布式架构是将系统的不同模块部署在多台服务器上,这些模块可以独立运行、协同工作,实现资源的最大化利用和服务的高可用性。分布式架构不仅解决了集群架构的数据一致性和扩展性问题,还提供了模块化的服务。
特点:
适用场景:分布式架构适合业务复杂度较高、不同模块有不同扩展需求的系统,如电商平台的商品、订单、用户、支付等模块可以独立分布式部署。
微服务架构是分布式架构的进一步演进,它通过将系统拆分为多个细粒度的服务,每个服务都是一个独立的功能单元,且服务之间通过 API 进行通信。每个微服务可以独立开发、部署和扩展,提升了系统的灵活性。
特点:
适用场景:微服务架构适用于复杂业务场景,特别是需要频繁迭代和更新的系统,如电商、社交网络和金融服务等。
分布式架构和微服务架构通过将系统拆分为独立的模块或服务,实现了更高的扩展性、容错性和灵活性,解决了集群架构在数据一致性、功能耦合等方面的局限性。这种解耦设计不仅提升了系统的三高能力(高并发、高可用、高性能),还增强了系统的敏捷性,能够更好地适应现代互联网应用的复杂需求。
接下来,我们将进一步探讨在分布式与微服务架构的基础上,如何从传统的 MVC 架构向领域驱动设计(DDD)架构演进,以更好地应对复杂业务场景。
我们先来看下我们目前传统的MVC架构模式下的项目架构示例。
下面是一个我们在开发中常用的传统 MVC 架构的 Java 项目的目录结构示例图。此结构通过模块划分和分层组织代码,便于开发、维护和扩展。以下是各个目录的详细说明:
project-root/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── example/
│ │ │ ├── modules/ # 按业务模块划分
│ │ │ │ ├── user/ # 用户模块
│ │ │ │ │ ├── controller/ # 用户模块控制器
│ │ │ │ │ │ └── UserController.java # 用户控制器
│ │ │ │ │ ├── model/ # 用户模块模型
│ │ │ │ │ │ └── User.java # 用户模型
│ │ │ │ │ ├── service/ # 用户模块服务
│ │ │ │ │ │ ├── UserService.java # 用户服务接口
│ │ │ │ │ │ └── UserServiceImpl.java # 用户服务实现类
│ │ │ │ │ ├── repository/ # 用户模块数据仓储
│ │ │ │ │ │ ├── UserRepository.java # 用户仓储接口
│ │ │ │ │ │ └── UserRepositoryImpl.java # 用户仓储实现类
│ │ │ │ │ └── validator/ # 用户数据验证器
│ │ │ │ │ └── UserValidator.java # 用户数据验证
│ │ │ │ └── product/ # 产品模块
│ │ │ │ ├── controller/ # 产品模块控制器
│ │ │ │ │ └── ProductController.java # 产品控制器
│ │ │ │ ├── model/ # 产品模块模型
│ │ │ │ │ └── Product.java # 产品模型
│ │ │ │ ├── service/ # 产品模块服务
│ │ │ │ │ ├── ProductService.java # 产品服务接口
│ │ │ │ │ └── ProductServiceImpl.java # 产品服务实现类
│ │ │ │ ├── repository/ # 产品模块数据仓储
│ │ │ │ │ ├── ProductRepository.java # 产品仓储接口
│ │ │ │ │ └── ProductRepositoryImpl.java # 产品仓储实现类
│ │ │ │ └── validator/ # 产品数据验证器
│ │ │ │ └── ProductValidator.java # 产品数据验证
│ │ │ ├── config/ # 配置文件和配置类
│ │ │ │ ├── WebConfig.java # Web应用配置
│ │ │ │ └── DatabaseConfig.java # 数据库配置
│ │ │ ├── middleware/ # 中间件
│ │ │ │ ├── AuthMiddleware.java # 认证中间件
│ │ │ │ └── LoggingMiddleware.java # 日志中间件
│ │ │ └── util/ # 通用工具类
│ │ │ ├── DateUtil.java # 日期工具类
│ │ │ ├── JsonUtil.java # JSON工具类
│ │ │ └── ValidatorUtil.java # 验证工具类
│ │ ├── resources/ # 资源文件
│ │ │ ├── templates/ # 视图模板文件
│ │ │ │ ├── user/
│ │ │ │ │ └── profile.html # 用户个人资料页面
│ │ │ │ └── product/
│ │ │ │ └── detail.html # 产品详情页面
│ │ │ └── application.properties # 应用程序配置文件
│ │ └── webapp/
│ │ └── WEB-INF/
│ │ ├── web.xml # Web应用配置文件
│ │ └── views/ # 视图层(可选:用于JSP文件)
│ │ ├── user.jsp # 用户页面视图
│ │ └── product.jsp # 产品页面视图
├── test/ # 测试目录
│ └── java/
│ └── com/
│ └── example/
│ ├── user/
│ │ ├── UserServiceTest.java # 用户服务测试类
│ │ └── UserRepositoryTest.java # 用户仓储测试类
│ └── product/
│ ├── ProductServiceTest.java # 产品服务测试类
│ └── ProductRepositoryTest.java # 产品仓储测试类
├── pom.xml # Maven构建文件
└── README.md # 项目说明文件
modules:业务模块层,将系统按业务划分为不同的模块,例如 user
和 product
。每个模块包含控制器、模型、服务、仓储和验证器等,便于分工和维护。
controller:控制器层,负责处理 HTTP 请求,控制流程。
UserController
和 ProductController
:处理用户和产品模块的请求逻辑。model:模型层,定义业务实体类(如 User
和 Product
),用于数据表示和传输。
service:服务层,包含业务逻辑的接口和实现。
UserService
和 ProductService
:接口,定义业务逻辑的操作方法。UserServiceImpl
和 ProductServiceImpl
:实现类,完成具体的业务逻辑。repository:仓储层,负责数据持久化和数据库访问。
UserRepository
和 ProductRepository
:接口,定义数据操作方法。UserRepositoryImpl
和 ProductRepositoryImpl
:具体实现类,负责与数据库交互。validator:验证层,负责数据的验证逻辑。
UserValidator
和 ProductValidator
:验证数据的合法性,确保数据符合业务需求。config:配置文件目录,包含应用和数据库的配置类。
WebConfig
:Web应用的配置类。DatabaseConfig
:数据库的配置类,包含数据库连接和其他设置。middleware:中间件层,处理跨业务逻辑的通用逻辑。
AuthMiddleware
:处理认证和权限控制。LoggingMiddleware
:记录请求和响应的日志。util:通用工具类,包含项目的公共功能。
DateUtil
、JsonUtil
、ValidatorUtil
:分别提供日期处理、JSON格式化和数据验证的辅助方法。resources:资源文件夹,包含静态资源和配置文件。
templates
:存放 HTML 模板文件,用于视图渲染。application.properties
:应用的配置文件,包含数据库连接、端口设置等。web.xml
:配置文件,定义 Web 应用的部署配置。views
:包含 JSP 文件,用于显示页面内容(适用于传统的 JSP 页面)。UserServiceTest
、UserRepositoryTest
:测试类,用于验证用户服务和仓储的正确性。ProductServiceTest
、ProductRepositoryTest
:测试类,用于验证产品服务和仓储的正确性。在这种分层架构中,当用户发起请求时,系统会按照以下流程处理请求,从而完成数据的获取、处理和返回。以下是请求流程的详细步骤:
用户发起请求
用户在浏览器或客户端通过 HTTP 请求访问应用的某个 URL(例如 /user/profile
),请求被发送到服务器。
请求被路由到 Controller
服务器根据请求的 URL,将请求路由到对应的控制器(Controller)方法。例如,请求 /user/profile
会被路由到 UserController
中处理用户资料的具体方法。这一步通常由框架的路由机制(如 Spring MVC 的 @RequestMapping
)实现。
Controller 处理请求并调用 Service 层
在 Controller 中,方法会对请求数据(例如请求参数、表单数据等)进行初步处理,然后调用 Service 层的相应方法。Controller 负责控制请求流程,并决定调用哪些业务逻辑。
UserController
中的 getUserProfile
方法可能会调用 UserService
中的 getUserById
方法来获取用户数据。Service 层执行业务逻辑
Service 层负责具体的业务逻辑处理,确保实现复杂业务需求。Service 层会根据业务规则,调用不同的仓储方法或执行必要的数据处理。
UserService
的 getUserById
方法中,可能会包含数据验证、权限检查等逻辑,并调用 DAO 层的接口来获取用户数据。Service 层调用 Repository(DAO 层)进行数据访问
Service 层通过调用 DAO 层接口来执行数据存储或查询操作。DAO 层负责与数据库交互,执行 CRUD 操作(创建、读取、更新、删除)。
UserService
调用 UserRepository
的 findById
方法,根据用户 ID 从数据库中获取用户记录。DAO 层从数据库中获取数据
DAO 层通过数据库查询语句(如 SQL)或 ORM(对象关系映射)框架(如 Hibernate)来从数据库中获取数据。查询结果将被映射为对应的模型对象(如 User
对象),然后返回给 Service 层。
UserRepository
实现 findById
方法,通过 SQL 查询获取 User
表中的数据,转换成 User
实体对象。Service 层接收数据并处理后返回给 Controller
Service 层在接收到 DAO 层返回的数据后,可能会对数据进行进一步处理,例如格式化、计算等操作。然后,将处理后的数据返回给 Controller。
UserService
接收到 UserRepository
返回的用户数据后,可能会对数据进行安全处理,过滤敏感信息。Controller 将数据返回给用户
Controller 接收到 Service 层返回的数据后,将数据封装成 HTTP 响应,通常以 JSON 或 HTML 的形式返回给前端。
UserController
将用户资料数据转换为 JSON 格式,返回给前端浏览器或客户端。前端接收响应并显示数据
前端接收服务器返回的响应数据,根据数据内容进行渲染或更新界面,从而完成一次完整的请求处理流程。
传统 MVC 架构的请求流程可以总结如下:
用户请求 -> 路由 -> Controller -> Service -> Repository -> 数据库
↑ ↓
业务逻辑处理 <- 数据访问
在这个流程中,每一层都有清晰的职责分工:
这种结构通过模块化和分层设计,使得代码组织更加清晰,便于团队协作和扩展功能。
下面是一个传统 PHP 项目的 MVC 架构目录结构示例,适合业务复杂的大型应用。该结构通过模块划分和分层实现了代码的清晰组织,使得项目便于维护和扩展。
project-root/
├── app/
│ ├── Modules/ # 业务模块(每个模块一个文件夹)
│ │ ├── User/ # 用户模块
│ │ │ ├── Controllers/ # 用户模块控制器
│ │ │ ├── Models/ # 用户模块模型
│ │ │ ├── Services/ # 用户模块服务
│ │ │ ├── Repositories/ # 用户模块数据仓储
│ │ │ └── Validators/ # 用户模块验证器
│ │ └── Product/ # 产品模块
│ │ ├── Controllers/ # 产品模块控制器
│ │ ├── Models/ # 产品模块模型
│ │ ├── Services/ # 产品模块服务
│ │ ├── Repositories/ # 产品模块数据仓储
│ │ └── Validators/ # 产品模块验证器
│ ├── Common/ # 通用模块(公共库、工具、帮助函数)
│ │ ├── Helpers/ # 公共帮助函数
│ │ │ └── ArrayHelper.php # 数组处理帮助类
│ │ ├── Traits/ # 通用特性
│ │ │ └── Timestampable.php # 通用时间戳特性
│ │ ├── Exceptions/ # 自定义异常
│ │ │ └── ValidationException.php # 验证异常
│ │ └── Interfaces/ # 通用接口
│ │ └── Loggable.php # 日志接口
│ ├── Core/ # 核心模块(应用核心、服务容器)
│ │ ├── Kernel.php # 应用核心类
│ │ └── Container.php # 服务容器
│ ├── Providers/ # 服务提供者(注册服务和依赖)
│ │ ├── DatabaseServiceProvider.php # 数据库服务提供者
│ │ └── CacheServiceProvider.php # 缓存服务提供者
│ └── Middleware/ # 中间件
│ ├── AuthMiddleware.php # 认证中间件
│ └── CsrfMiddleware.php # CSRF 中间件
├── config/ # 配置文件
│ ├── app.php # 应用配置文件
│ └── database.php # 数据库配置文件
├── public/ # 公共目录(用于Web访问的入口)
│ ├── index.php # 前端控制器(应用入口)
│ └── assets/ # 静态资源文件(如有需要)
├── routes/
│ └── web.php # 路由定义文件
├── storage/ # 存储目录
│ ├── logs/ # 日志文件
│ └── cache/ # 缓存文件
├── vendor/ # Composer依赖文件
├── .env # 环境变量文件
└── composer.json # Composer 配置文件
app/Modules:业务模块层,将应用系统按照业务划分成不同模块(如 User
和 Product
),便于代码组织和模块化开发。
app/Common:通用模块,包含所有模块共用的类和工具函数。
app/Core:核心模块,包含应用的核心类和服务容器。
app/Providers:服务提供者,注册和初始化各类服务,例如数据库、缓存服务。
app/Middleware:中间件层,处理请求和响应的拦截逻辑。
config:配置文件夹,包含应用和数据库等配置文件。
public:公共目录,包含对外可访问的文件。
routes:路由文件夹,定义 URL 和控制器方法之间的映射关系。
storage:存储目录,用于保存日志、缓存文件等运行时数据。
vendor:通过 Composer 安装的第三方依赖库。
.env:环境变量文件,用于配置敏感信息和环境变量。
composer.json:Composer 配置文件,用于定义项目的依赖和其他配置。
public/index.php
前端控制器,初始化应用,并加载所有配置和依赖。routes/web.php
中定义的路由规则,请求被分配到对应的 Controller(如 UserController
),Controller 接收并处理请求数据。通过这种分层的 MVC 架构,PHP 项目能够更好地分离关注点,使业务逻辑、数据访问、请求控制等职责各司其职,从而提升代码的可维护性和可扩展性。
在开发小型或中型项目时,我们通过上面示例展示的MVC 架构凭借其简单的分层设计可以很好地满足需求。然而,当面对大型复杂项目的业务需求时,MVC 架构的弊端逐渐显现出来。随着业务的不断复杂化,无论是 DAO 层、Domain 层还是 Service 层,代码都变得越来越庞大,服务之间的依赖和引用也变得复杂且混乱。这种架构在大型项目中带来了以下问题和风险:
代码臃肿,难以维护
模块间耦合度高,系统灵活性差
业务逻辑分散,难以理解和测试
数据一致性和事务控制困难
难以适应业务变化
团队协作效率低,职责划分模糊
难以扩展到分布式架构
综上所述,传统的 MVC 架构虽然简单有效,但在应对大型复杂项目时显得力不从心,带来了代码臃肿、模块耦合度高、维护困难、测试复杂等问题。因此,为了解决这些问题,提升代码的可维护性、可扩展性和业务适应性,传统 MVC 架构逐渐向领域驱动设计(DDD)演进。这种设计模式能够更好地组织复杂的业务逻辑,明确职责分工,使系统架构更贴近业务需求,帮助开发团队更高效地应对复杂项目的挑战。
领域驱动设计(Domain-Driven Design, DDD)是一种以业务需求为核心的架构设计方法,强调从业务视角来构建系统。DDD 的核心思想是将复杂的业务逻辑和系统的实现方式紧密结合,通过领域模型来清晰地表达业务含义,使系统架构能够直接反映业务需求,从而更好地应对复杂的业务场景。
DDD 通过引入“领域模型”的概念,将系统按照业务功能划分为多个领域模块,每个领域模块都代表了一个业务核心。每个领域模块包含其独有的实体、值对象、聚合、仓储和服务等组件,这些组件共同构建了业务的逻辑和数据结构。
在 DDD 中,系统被划分为不同的限界上下文,每个上下文代表一个业务领域,内部包含多个核心概念:
实体(Entity)
实体是具有唯一标识的业务对象,在业务操作中有生命周期的概念。例如,订单、用户等都可以是实体对象。实体具有业务逻辑和数据属性,并随着业务的变化而发生状态变化。
值对象(Value Object)
值对象是没有唯一标识的对象,通常用来描述某些特定的属性组合。值对象是不可变的,例如地址(包含省、市、街道等字段),其属性一旦创建后通常不会变化。
聚合(Aggregate)和聚合根(Aggregate Root)
聚合是实体和值对象的集合,它是业务逻辑和数据的一致性边界。每个聚合都由一个聚合根(Aggregate Root)管理,聚合根是聚合的唯一入口点,负责确保聚合内部的数据一致性。例如,订单聚合根可能包含订单明细的多个实体,订单聚合根负责整个订单的逻辑和一致性。
仓储(Repository)
仓储是用于持久化聚合的组件,它通过聚合根来管理聚合的持久化和数据访问。仓储隐藏了数据存储的细节,为应用提供了简单的接口来操作数据。例如,订单仓储负责订单和订单明细的存取,屏蔽了具体的数据库访问逻辑。
服务(Service)
服务是用于实现无特定对象的业务逻辑的组件。一般分为领域服务和应用服务:
限界上下文(Bounded Context)
限界上下文是领域模型的边界,定义了不同领域之间的隔离和独立性。在一个大型系统中,不同的上下文代表了不同的业务逻辑区域,它们之间通过清晰的接口进行交互。例如,一个电商系统中可以有“订单上下文”和“用户上下文”等限界上下文。
DDD 架构通常被分为以下四层,每层承担不同的职责:
清晰的业务逻辑
DDD 通过领域模型将业务逻辑清晰地划分在领域层中,使得业务逻辑更具语义化。系统的结构能够直接映射业务流程,便于理解和维护。
提高系统的可维护性和扩展性
通过限界上下文和聚合的概念,DDD 架构有效地隔离了不同模块的业务逻辑,使得各个模块可以独立演化、更新和维护。
支持复杂的业务需求
DDD 架构针对复杂的业务场景设计,支持复杂的业务逻辑、规则和数据一致性。通过聚合和聚合根的设计,能够更好地处理跨模块的数据一致性问题。
增强团队协作
通过限界上下文的定义,团队可以围绕业务需求组织工作,每个上下文的模型是相对独立的,可以由不同团队负责,减少团队间的冲突,提升协作效率。
在传统的 MVC 架构中,项目通常以 Controller、Service、Repository 等方式进行模块化管理,缺乏清晰的领域边界和职责划分。转换成 DDD 架构后,我们将根据业务逻辑进行更清晰的分层,将系统分为限界上下文和领域模型,以便更好地支持复杂业务需求。
以下是项目的目录结构从传统 MVC 架构转变为 DDD 架构后的示例:
project-root/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── example/
│ │ │ ├── context/ # 限界上下文目录
│ │ │ │ ├── user/ # 用户限界上下文
│ │ │ │ │ ├── application/ # 应用层
│ │ │ │ │ │ ├── UserApplicationService.java # 用户应用服务
│ │ │ │ │ ├── domain/ # 领域层
│ │ │ │ │ │ ├── model/ # 用户领域模型
│ │ │ │ │ │ │ ├── User.java # 用户实体
│ │ │ │ │ │ │ ├── UserAddress.java # 值对象,用户地址
│ │ │ │ │ │ ├── repository/ # 用户仓储接口
│ │ │ │ │ │ │ └── UserRepository.java # 用户仓储接口
│ │ │ │ │ │ ├── service/ # 领域服务
│ │ │ │ │ │ │ └── UserDomainService.java # 用户领域服务
│ │ │ │ │ ├── infrastructure/ # 基础设施层
│ │ │ │ │ │ ├── repository/ # 仓储实现
│ │ │ │ │ │ │ └── UserRepositoryImpl.java # 用户仓储实现类
│ │ │ │ │ │ ├── persistence/ # 持久化管理
│ │ │ │ │ │ │ └── UserJpaEntity.java # 用户 JPA 实体
│ │ │ │ │ │ ├── mapper/ # 数据传输对象(DTO)映射
│ │ │ │ │ │ │ └── UserMapper.java # 用户领域对象与DTO映射
│ │ │ │ │ ├── interfaces/ # 用户接口层
│ │ │ │ │ │ ├── api/ # 对外 API 接口
│ │ │ │ │ │ │ └── UserController.java # 用户控制器
│ │ │ │ ├── product/ # 产品限界上下文
│ │ │ │ ├── application/ # 应用层
│ │ │ │ │ ├── ProductApplicationService.java # 产品应用服务
│ │ │ │ ├── domain/ # 领域层
│ │ │ │ │ ├── model/ # 产品领域模型
│ │ │ │ │ │ ├── Product.java # 产品实体
│ │ │ │ │ ├── repository/ # 产品仓储接口
│ │ │ │ │ │ └── ProductRepository.java # 产品仓储接口
│ │ │ │ │ ├── service/ # 领域服务
│ │ │ │ │ └── ProductDomainService.java # 产品领域服务
│ │ │ │ ├── infrastructure/ # 基础设施层
│ │ │ │ │ ├── repository/ # 产品仓储实现
│ │ │ │ │ │ └── ProductRepositoryImpl.java # 产品仓储实现类
│ │ │ │ │ ├── persistence/ # 持久化管理
│ │ │ │ │ │ └── ProductJpaEntity.java # 产品 JPA 实体
│ │ │ │ │ ├── mapper/ # DTO 映射
│ │ │ │ │ └── ProductMapper.java # 产品领域对象与DTO映射
│ │ │ │ ├── interfaces/ # 产品接口层
│ │ │ │ ├── api/ # 对外 API 接口
│ │ │ │ │ └── ProductController.java # 产品控制器
│ │ ├── config/ # 配置文件和配置类
│ │ │ ├── WebConfig.java # Web应用配置
│ │ │ └── DatabaseConfig.java # 数据库配置
│ ├── resources/ # 资源文件
│ │ ├── application.properties # 应用程序配置文件
├── test/ # 测试目录
│ └── java/
│ └── com/
│ └── example/
│ ├── user/
│ │ ├── UserApplicationServiceTest.java # 用户应用服务测试类
│ │ └── UserRepositoryTest.java # 用户仓储测试类
│ └── product/
│ ├── ProductApplicationServiceTest.java # 产品应用服务测试类
│ └── ProductRepositoryTest.java # 产品仓储测试类
├── pom.xml # Maven 构建文件
└── README.md # 项目说明文件
限界上下文(context)
根据 DDD 的设计原则,将系统拆分为多个限界上下文(Bounded Context),每个上下文对应一个独立的业务领域。这里的限界上下文包括“用户”和“产品”两个模块,每个模块下各自拥有应用层、领域层、基础设施层和接口层。
应用层(application)
应用层负责定义业务流程和用例逻辑,通常由应用服务(如 UserApplicationService
、ProductApplicationService
)组成。应用层主要负责协调领域层的各种对象,处理用户请求的业务流程,而不包含具体的业务规则。
领域层(domain)
领域层是 DDD 的核心,包含领域模型、领域服务和仓储接口:
User
、Product
),将业务逻辑封装在实体和值对象中。UserRepository
、ProductRepository
),管理实体的持久化和检索,隔离数据库访问。UserDomainService
、ProductDomainService
),用于处理涉及多个领域对象的业务逻辑,确保业务规则在领域层实现。基础设施层(infrastructure)
基础设施层为系统提供数据库、缓存、文件系统等基础支持。
UserRepositoryImpl
)在此层实现,应用层和领域层可以通过接口访问数据库。UserJpaEntity
使用 JPA 实体映射。接口层(interfaces)
接口层负责系统的对外通信,包括 API 控制器(如 UserController
、ProductController
),将请求传递给应用层并将处理结果返回给用户。
配置(config)
配置文件和服务类如 WebConfig
、DatabaseConfig
等用于项目整体的系统配置。
在传统的 PHP MVC 架构中,项目的目录结构是以 Controllers、Models、Services、Repositories 等分类组织的,缺乏清晰的领域划分和模块隔离。通过 DDD 的重构,我们可以按照领域模型的设计原则,将项目划分为多个限界上下文和层次结构,以支持更复杂的业务逻辑和更高的扩展性。
以下是 PHP 项目从传统 MVC 架构演变为 DDD 架构后的目录结构示例:
project-root/
├── app/
│ ├── Contexts/ # 限界上下文(按业务领域划分)
│ │ ├── User/ # 用户限界上下文
│ │ │ ├── Application/ # 应用层
│ │ │ │ └── Services/
│ │ │ │ └── UserApplicationService.php # 用户应用服务
│ │ │ ├── Domain/ # 领域层
│ │ │ │ ├── Models/ # 用户领域模型
│ │ │ │ │ └── User.php # 用户实体
│ │ │ │ ├── Repositories/ # 仓储接口
│ │ │ │ │ └── UserRepositoryInterface.php # 用户仓储接口
│ │ │ │ ├── Services/ # 领域服务
│ │ │ │ │ └── UserDomainService.php # 用户领域服务
│ │ │ │ ├── ValueObjects/ # 值对象
│ │ │ │ │ └── UserAddress.php # 用户地址值对象
│ │ │ ├── Infrastructure/ # 基础设施层
│ │ │ │ ├── Repositories/ # 仓储实现
│ │ │ │ │ └── UserRepository.php # 用户仓储实现类
│ │ │ │ └── Persistence/ # 数据持久化
│ │ │ │ └── UserMapper.php # 用户对象映射器
│ │ │ ├── Interfaces/ # 接口层
│ │ │ │ ├── Controllers/ # 用户模块控制器
│ │ │ │ │ └── UserController.php # 用户控制器
│ │ │ │ ├── DTOs/ # 数据传输对象
│ │ │ │ │ └── UserDTO.php # 用户DTO
│ │ ├── Product/ # 产品限界上下文
│ │ │ ├── Application/ # 应用层
│ │ │ │ └── Services/
│ │ │ │ └── ProductApplicationService.php # 产品应用服务
│ │ │ ├── Domain/ # 领域层
│ │ │ │ ├── Models/ # 产品领域模型
│ │ │ │ │ └── Product.php # 产品实体
│ │ │ │ ├── Repositories/ # 仓储接口
│ │ │ │ │ └── ProductRepositoryInterface.php # 产品仓储接口
│ │ │ │ ├── Services/ # 领域服务
│ │ │ │ │ └── ProductDomainService.php # 产品领域服务
│ │ │ │ ├── ValueObjects/ # 值对象
│ │ │ │ └── ProductPrice.php # 产品价格值对象
│ │ │ ├── Infrastructure/ # 基础设施层
│ │ │ │ ├── Repositories/ # 仓储实现
│ │ │ │ │ └── ProductRepository.php # 产品仓储实现类
│ │ │ │ └── Persistence/ # 数据持久化
│ │ │ │ └── ProductMapper.php # 产品对象映射器
│ │ │ ├── Interfaces/ # 接口层
│ │ │ │ ├── Controllers/ # 产品模块控制器
│ │ │ │ │ └── ProductController.php # 产品控制器
│ │ │ │ ├── DTOs/ # 数据传输对象
│ │ │ │ └── ProductDTO.php # 产品DTO
│ ├── Common/ # 通用模块(公共库、工具、帮助函数)
│ │ ├── Helpers/ # 公共帮助函数
│ │ │ └── ArrayHelper.php # 数组处理帮助类
│ │ ├── Traits/ # 通用特性
│ │ │ └── Timestampable.php # 通用时间戳特性
│ │ ├── Exceptions/ # 自定义异常
│ │ │ └── DomainException.php # 领域异常
│ │ └── Interfaces/ # 通用接口
│ │ └── Loggable.php # 日志接口
│ ├── Core/ # 核心模块(应用核心、服务容器)
│ │ ├── Kernel.php # 应用核心类
│ │ └── Container.php # 服务容器
│ ├── Providers/ # 服务提供者(注册服务和依赖)
│ │ ├── DatabaseServiceProvider.php # 数据库服务提供者
│ │ └── CacheServiceProvider.php # 缓存服务提供者
│ └── Middleware/ # 中间件
│ ├── AuthMiddleware.php # 认证中间件
│ └── CsrfMiddleware.php # CSRF 中间件
├── config/ # 配置文件
│ ├── app.php # 应用配置文件
│ └── database.php # 数据库配置文件
├── public/ # 公共目录(用于 Web 访问的入口)
│ ├── index.php # 前端控制器(应用入口)
│ └── assets/ # 静态资源文件(如有需要)
├── routes/
│ └── web.php # 路由定义文件
├── storage/ # 存储目录
│ ├── logs/ # 日志文件
│ └── cache/ # 缓存文件
├── vendor/ # Composer 依赖文件
├── .env # 环境变量文件
└── composer.json # Composer 配置文件
限界上下文(Contexts)
DDD 中通过“限界上下文”划分不同的业务模块(如“User” 和 “Product”),每个上下文包含领域的所有组件,避免了传统 MVC 架构中模块耦合和职责不清的问题。
应用层(Application)
应用层包含具体的应用服务,负责管理业务流程,不包含业务规则。通过 UserApplicationService
和 ProductApplicationService
,处理接口请求,并协调领域对象执行操作。
领域层(Domain)
领域层是 DDD 架构的核心,包括:
User
、Product
,每个对象包含独立的业务逻辑。UserRepositoryInterface
、ProductRepositoryInterface
,支持数据的持久化和检索。UserDomainService
、ProductDomainService
。UserAddress
和 ProductPrice
,代表不可变的业务属性。基础设施层(Infrastructure)
基础设施层支持领域层和应用层的运行:
UserRepository
和 ProductRepository
,实现仓储接口,用于实际的数据库操作。UserMapper
、ProductMapper
,用于将领域对象与数据库记录映射。接口层(Interfaces)
接口层是对外的通信接口,包含:
UserController
和 ProductController
,接收 HTTP 请求并调用应用服务,完成请求响应。UserDTO
、ProductDTO
,用于在接口层和应用层之间传递数据。通用模块(Common)
包含各限界上下文共享的通用库、异常和工具函数,如 ArrayHelper
、Timestampable
等。
在 DDD 架构中,通过限界上下文将项目按业务领域划分,各模块具备独立性,且分层明确。领域层作为核心,专注于复杂业务逻辑的实现,而应用层负责业务流程控制,基础设施层和接口层提供支持和对外接口。通过这样的改造,PHP 项目的架构得到了极大优化,更能适应复杂业务场景的需求。
在当今复杂的业务场景中,传统的 MVC 架构在应对不断增长的需求和系统复杂度时逐渐显现出不足。随着系统功能的增多,MVC 架构中的控制器、服务和数据访问层的代码量快速膨胀,导致模块之间的耦合度增加、职责模糊,维护难度上升。传统的 MVC 架构虽能实现简单的分层组织,但在面对复杂业务逻辑和快速变化的需求时,难以确保系统的可扩展性、灵活性和可维护性。
MVC 架构的不足主要体现在以下方面:
DDD 架构提供了一种以业务领域为核心的设计思路,通过清晰的领域模型组织业务逻辑,解决了 MVC 的许多局限性。DDD 通过限界上下文、聚合、领域服务和仓储等概念,将系统按照业务逻辑划分为多个独立的领域模块,使业务逻辑更加集中和清晰,解决了传统 MVC 架构的以下问题:
综上所述,MVC 与 DDD 的核心区别在于架构设计的关注点不同。MVC 更关注代码的层次分离,适用于较简单的系统,而 DDD 则以业务领域为中心,强调对复杂业务逻辑的组织与管理。在现代复杂业务场景中,DDD 提供了更灵活、更具扩展性的架构模式,使系统能适应不断变化的需求。
最终,通过将传统 MVC 架构重构为 DDD 架构,我们可以在业务快速演进的环境中构建更加稳定、可扩展和易于维护的系统,满足现代企业对系统高效性和业务适应性的需求。
在系统架构演进的过程中,从最初的单层架构到后来的 MVC 架构,再到如今流行的 DDD 架构,这不仅是技术层面的改进,更是对业务理解逐步深化的过程。DDD 不只是一个架构模式,更是一种帮助我们探索和解决复杂业务问题的方法论。通过将 DDD 思想融入架构设计,我们能够更全面地适应并应对不断变化的业务需求,使系统具有更高的弹性和适应能力。这种架构演进的思维方式,为将来应对更复杂的业务挑战奠定了坚实的基础。