DDD 向来以高门槛而文明,他内部提出了非常多且抽象晦涩难懂的概念,比如实体、值对象、领域服务、领域事件、聚合根、工厂、仓库、应用服务等,第一批涌入人员很多被这些概念击退,少数坚持下来爱好学习的人继续往深处走,迎接他们的是更多的概念,比如 CQRS、六边形架构的内六边形&外六边形、输入适配器、输出适配器、防腐层、开放主机……
少数异常坚韧者熬了无数次通宵,终于将这些概念搞明白。一边感慨设计的精妙,可以形成科学且高度结构化的解决方案。一边又在摇头,叹息落地实现的难度,自己融会贯通已经这么艰难,还怎么奢求整个团队能步调一致。
追求到手真的是一场空吗?
这个问题在很长一段时间内一直困扰着我,按高度结构化进行开发,成本太高,很多关键的类和扩展点自己都没有办法记牢。不按结构化进行开发,代码就会失控,各种逻辑耦合在一起,几千甚至上万行代码的方法慢慢涌现,项目逐渐走向失控。
直到我将 结构化、标准化、模版化 这三个概念放在一起考虑,才真正豁然开朗。
-
结构化:DDD、CQRS 给出的解决方案都是高度结构化的方案,每个组件的边界和职责清晰明了,组件间的交互关系都有明确的规则。
-
标准化:在结构化的基础上,每个组件都具备高度的标准化。尽管承载的业务流程不同,但每个组件的设计都遵循同样的规则。
-
模版化:标准化意味着会有大量重复逻辑(不是重复代码),这些重复逻辑在开发手里就是 复制-粘帖-稍作修改。
既然是重复工作那就应该由机器完成!
基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
-
项目地址:https://github.com/YunaiV/ruoyi-vue-pro
-
视频教程:https://doc.iocoder.cn/video/
目的非常明确:
-
-
统一规范,业务流程、组件设计在团队内保存高度一致。
-
提升开发效率,逻辑重复部分交由机器完成,提升开发效率。
基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
-
项目地址:https://github.com/YunaiV/yudao-cloud
-
视频教程:https://doc.iocoder.cn/video/
Maven 脚手架主要实现整个项目的结构高度统一,降低新项目构建成本。
使用以下命令,可快速创建符合公司规范的项目:
mvn archetype:generate -DarchetypeGroupId=com.geekhalo.lego -DarchetypeArtifactId=services-archetype -DarchetypeVersion=0.1.39-plugin_demo-SNAPSHOT -DgroupId=com.geekhalo -DartifactId=user -Dversion=0.1.39-plugin_demo-SNAPSHOT
该命令行是基于 Maven 的构建工具,用于生成项目骨架。具体来说,这个命令执行的是 archetype 插件的
generate
目标,用于创建一个预定义项目结构的模板实例。
参数解释如下:
-
-DarchetypeGroupId=com.geekhalo.lego
: 指定要使用的原型(archetype)所在的组ID,这是一个自定义的 Maven 组织标识符,对应于提供项目的骨架模板的组织或团体。
-
-DarchetypeArtifactId=services-archetype
: 指定要使用的原型的工件ID,这是特定于该组织下用于生成新项目的模板名称。
-
-DarchetypeVersion=0.1.39-plugin_demo-SNAPSHOT
: 设置所用原型版本,这里是一个快照版本号,表明它可能在开发过程中频繁更新。
-
-DgroupId=com.geekhalo
: 为将要生成的新项目设置组ID,这是新项目所属的Maven组织标识符。
-
-DartifactId=user
: 设置新项目的工件ID,即新项目的名字。
-
-Dversion=0.1.39-plugin_demo-SNAPSHOT
: 设置新项目的初始版本号,与原型版本保持一致,同样使用了一个快照版本。
综上所述,这条命令的作用是在本地通过Maven生成一个新的项目结构,该项目是基于
com.geekhalo.lego
组下的
services-archetype
模板,并且初始化时设置的项目信息是
groupId=com.geekhalo
,
artifactId=user
,以及
version=0.1.39-plugin_demo-SNAPSHOT
。
命令执行完成后,你便可以看到新增符合公司规范的 user 模块:
这个项目是一个用户服务,它被划分为多个模块:
-
user-domain
:包含了用户服务的核心领域模型和领域逻辑。它是六边形架构中的核心层,不直接依赖于外部系统或技术栈。
-
user-infrastructure
:包含了用户服务的具体实现细节和技术栈相关的代码。例如,数据库访问层、消息队列客户端等。
-
user-app
:实现了用户服务的应用逻辑。它依赖于其他模块(如
user-domain
、
user-infrastructure
)来完成业务功能。
-
user-api
:定义了用户服务对外提供的接口,也就是服务契约,包括 基于Feign的RPC契约 和 基于RocketMQ的消息契约。
-
user-feign-service
:如果用户服务需要提供给其他微服务调用,那么这里会定义 Feign 服务接口。
-
user-feign-client
:是用户服务对外提供的 SDK,它提供了对用户服务的简单易用的 API 接口,使得其他服务能够快速地集成和调用用户服务的功能。
-
user-bootstrap
:启动用户服务的引导程序。它通常包含 Spring Boot 的启动类和一些配置文件。
在根目录下,有两个重要的文件:
-
pom.xml
:Maven 项目的配置文件,用于管理所有子模块的依赖关系和构建过程。
-
README.md
:项目的说明文档,通常包括项目简介、如何运行、如何开发等内容。
结构没有对错,不同的公司存在不同规范,这块不是关注的重点。
有了项目骨架后,接下来的重点就是业务开发。通过自定义 idea 插件,可以将规范融合到插件内,保障规范落地的同时,大幅降低开发成本。
聚合根是DDD中最为重要的一个概念,也是承接业务逻辑的最小单元。
日常开发基本都是围绕聚合根进行的,对此 idea 很多功能都是围绕聚合根进行构建。
让我们使用插件新建一个聚合根 “BasicUser” 用于存储用户的基本信息。在 domain 模块下的 basic 包上点击右键,选择 lego 菜单下的 “创建 聚合根” 功能,具体如下:
在弹出的对话框中填入 聚合根类名为“BasicUser”,其他保存默认,详见:
点击左上角的 View,切换到视图模型配置:
点击 “OK” 按钮,观察项目变化:Domain 模块:
Infrastructure 模块:
App 模块:
创建一个简单的 BasicUser 聚合根,插件为我们生成一系列文件。这些文件之前都需手工完成,浪费了大量时间。
从设计上,项目采用 CQRS 进行设计,需要同时应对简单和复杂两个场景。换个说法就是:
-
业务通常从一个简单场景开始,需要满足快速开发的要求。
-
随着迭代的增加复杂性也随之增加,此时可以在不影响架构设计的前提下快速升级架构。
对于简单场景,推荐使用共享存储的 CQRS 架构,具体如下:
如果写操作和读操作两者差距巨大,推荐使用标准的 CQRS 架构,具体如下: