 
  mall学习教程官网:macrozheng.com
 1. 背景 DDD 向来以高门槛而文明,他内部提出了非常多且抽象晦涩难懂的概念,比如实体、值对象、领域服务、领域事件、聚合根、工厂、仓库、应用服务等,第一批涌入人员很多被这些概念击退,少数坚持下来爱好学习的人继续往深处走,迎接他们的是更多的概念,比如 CQRS、六边形架构的内六边形&外六边形、输入适配器、输出适配器、防腐层、开放主机……
  少数异常坚韧者熬了无数次通宵,终于将这些概念搞明白。一边感慨设计的精妙,可以形成科学且高度结构化的解决方案。一边又在摇头,叹息落地实现的难度,自己融会贯通已经这么艰难,还怎么奢求整个团队能步调一致。
  追求到手真的是一场空吗?
  这个问题在很长一段时间内一直困扰着我,按高度结构化进行开发,成本太高,很多关键的类和扩展点自己都没有办法记牢。不按结构化进行开发,代码就会失控,各种逻辑耦合在一起,几千甚至上万行代码的方法慢慢涌现,项目逐渐走向失控。
  直到我将 结构化、标准化、模版化 这三个概念放在一起考虑,才真正豁然开朗。
 - 结构化:DDD、CQRS 给出的解决方案都是高度结构化的方案,每个组件的边界和职责清晰明了,组件间的交互关系都有明确的规则。
 - 标准化:在结构化的基础上,每个组件都具备高度的标准化。尽管承载的业务流程不同,但每个组件的设计都遵循同样的规则。
 - 模版化:标准化意味着会有大量重复逻辑(不是重复代码),这些重复逻辑在开发手里就是 复制-粘帖-稍作修改。
 
  既然是重复工作那就应该由机器完成!
 2. 目的 目的非常明确:
 - 降低概念的记忆成本,让初级开发不在惧怕 DDD。
 - 统一规范,业务流程、组件设计在团队内保存高度一致。
 - 提升开发效率,逻辑重复部分交由机器完成,提升开发效率。
 
  这或许是一个对你有用的开源项目,mall项目是一套基于 SpringBoot + Vue + uni-app 实现的电商系统(Github标星60K),采用Docker容器化部署,后端支持多模块和微服务架构。包括前台商城项目和后台管理系统,能支持完整的订单流程!涵盖商品、订单、购物车、权限、优惠券、会员、支付等功能!
 - Boot项目:https://github.com/macrozheng/mall
 - Cloud项目:https://github.com/macrozheng/mall-swarm
 - 视频教程:https://www.macrozheng.com/video/
 
  项目演示:
  
 
 
 3. 功能介绍3.1. Maven 脚手架 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:项目的说明文档,通常包括项目简介、如何运行、如何开发等内容。
  结构没有对错,不同的公司存在不同规范,这块不是关注的重点。
 
 3.2. 自定义 Idea 插件 有了项目骨架后,接下来的重点就是业务开发。通过自定义 idea 插件,可以将规范融合到插件内,保障规范落地的同时,大幅降低开发成本。
 
 3.2.1. 创建聚合根和视图模型 聚合根是DDD中最为重要的一个概念,也是承接业务逻辑的最小单元。
 
  日常开发基本都是围绕聚合根进行的,对此 idea 很多功能都是围绕聚合根进行构建。
  让我们使用插件新建一个聚合根 “BasicUser” 用于存储用户的基本信息。在 domain 模块下的 basic 包上点击右键,选择 lego 菜单下的 “创建 聚合根” 功能,具体如下:
 
  
  在弹出的对话框中填入 聚合根类名为“BasicUser”,其他保存默认,详见:
 
  
  点击左上角的 View,切换到视图模型配置:
 
  
  点击 “OK” 按钮,观察项目变化:Domain 模块:
 
  
  Infrastructure 模块:
 
  
  App 模块:
 
  
  创建一个简单的 BasicUser 聚合根,插件为我们生成一系列文件。这些文件之前都需手工完成,浪费了大量时间。
  从设计上,项目采用 CQRS 进行设计,需要同时应对简单和复杂两个场景。换个说法就是:
 - 业务通常从一个简单场景开始,需要满足快速开发的要求。
 - 随着迭代的增加复杂性也随之增加,此时可以在不影响架构设计的前提下快速升级架构。
 
  对于简单场景,推荐使用共享存储的 CQRS 架构,具体如下:
 
  
  如果写操作和读操作两者差距巨大,推荐使用标准的 CQRS 架构,具体如下:
 
  
  简单介绍完背景后,看框架帮我们生成了什么:
 - 基于 DDD 的写流程
 - BasicUser:这是用户服务的核心领域对象,它包含了用户的属性信息以及与之相关的业务逻辑。作为领域聚合根,它可以确保数据的一致性和完整性,并且控制了对用户状态的修改操作。
 - AbstractBasicUserEvent:一个抽象事件,表示用户服务中发生的一些重要事件。这些事件可能会触发用户服务的状态变化或者引发其他行为。具体的事件类型可以通过继承该抽象类来实现。
 - BasicUserCommandRepository:这是用户服务的命令存储库,负责处理对用户对象的创建、更新和删除等操作。它通常会将这些操作持久化到数据库或其他存储介质中。
 - JpaBasedBasicUserCommandRepository:是用户服务的命令侧存储库的一个具体实现,它使用 JPA(Java Persistence API)来操作数据库。这种实现方式可以让开发者更专注于业务逻辑,而无需关心底层的数据访问细节。
 - BasicUserCommandApplication:这是用户服务的命令侧应用服务,它封装了用户服务的业务逻辑,并协调各个组件(如领域对象、存储库等)的工作。它接收来自客户端的请求,执行相应的业务逻辑,并返回结果。
 
 - 基于 View 的读流程
 - BasicUserView:是用户服务的查询侧视图模型,它包含了用户的基本信息,但不会包含任何业务逻辑。它的主要作用是在查询时提供快速响应的服务,以满足高并发读取的需求。
 - BasicUserQueryRepository:这是用户服务的查询侧存储库,负责处理对用户视图模型的查询操作。它可以从数据库、缓存或者其他快速的数据源中获取数据。
 - JpaBasedBasicUserQueryRepository:这是用户服务的查询侧存储库的一个具体实现,它使用 JPA(Java Persistence API)来操作数据库。这种实现方式可以让开发者更专注于业务逻辑,而无需关心底层的数据访问细节。
 - BasicUserQueryApplication:这是用户服务的查询应用服务,它封装了用户服务的查询逻辑,并协调各个组件(如视图模型、存储库等)的工作。它接收来自客户端的查询请求,执行相应的查询逻辑,并返回结果。
 
 
  idea 插件帮我们完成了大部分工作,快速生成共享存储的 CQRS 业务代码骨架。
 3.2.2. 创建聚合根方法 有了聚合根后,我们需要在聚合根上添加业务方法。在 BasicUser 类上右键选择 lego 下的 创建聚合根方法,如图所示:
 
  
  在弹窗中填入方法名为:create,如图所示:
 
  
  点击“OK”按钮,会有如下变化:
 
  
 
  
 
  
  自动生成信息如下:
 - 新创建的 create 目录,包含流程中的核心组件
 - CreateBasicUserCommand:这是一个创建用户的命令对象,它包含了创建用户所需的所有参数和信息。当客户端想要创建一个新的用户时,它会发送这个命令对象到用户服务。
 - CreateBasicUserContext:这是一个上下文对象,它包含了创建用户所需的环境信息和其他辅助信息,可以帮助操作流程更好地维护和共享数据。
 - BasicUserCreatedEvent:这是一个事件对象,表示用户已经被成功创建。当用户服务接收到 CreateBasicUserCommand 命令并成功创建了一个新的用户后,它会发布这个事件对象。
 
 - BasicUser 新增 create 业务方法
 - 静态的 create 方法,用于使用 Context 对象创建 BasicUser
 - 实例的 init 方法,用于对 BasicUser 进行核心状态设置、创建并发布领域事件
 
 - BasicUserCommandApplication 新增 create 方法
 - 输入  CreateBasicUserCommand 对象,协调内部组件,完成创建流程
 
 
  无需编写应用服务的代码,框架会自动生成 Proxy 类来完成全部流程。开发人员只需将精力放在 create 目录下的各种组件即可,组件的协作集成全部由 lego 框架完成。
 
  此时,你就已经具备了一个不含任何业务逻辑的 创建用户 流程。
 3.2.3. 创建查询方法 在 BasicUserQueryApplication 右键选择创建 “创建查询方法”,弹出如下对话框:
 
  
  我们选择分页查询,确定后,自动生成:
 @QueryServiceDefinition(         repositoryClass = BasicUserQueryRepository.class,         domainClass = BasicUserView.class ) @Validated public interface BasicUserQueryApplication {     // 分页查询方法     Page<BasicUserView> pageOf(@Valid PageByStatus query); }
  打开 PageByStatus 类,完善信息如下:
 @NoArgsConstructor @Data public class PageByStatus{     // 使用 status 字段进行过滤     @FieldEqualTo("status")     private UserStatus status;     // 分页信息     private Pageable pageable;     // 排序信息     private Sort sort; }
  此时什么都不需要做,你便拥有了一个检索接口。
  其中 PageByStatus 为 QueryObject,通过 注解 和 特定类型的字段声明查询能力:
 - @FieldEqualTo("status") 标明该字段将用于与 status 进行过滤
 - Pageable 类型的字段为分页信息,可以指定页码和每页大小,完成快速分页
 - Sort 类型的字段提供排序信息,用于对数据进行排序
 
 3.2.4. 其他支持 除了生成骨架代码外,框架对核心组件也提供了支持。
 3.2.4.1. 通用枚举 选择创建枚举,弹出如下对话框:
 
  
  点击确定,自动生成枚举和Jpa 转化器,如果使用 MyBatis 也可以选择生成 TypeHandler。
  UserStatus 代码如下:
 public enum UserStatus implements CommonEnum {     ;     // code 为枚举的唯一标识     private final int code;     // 枚举描述信息     private final String descr;
      UserStatus(int code, String descr){         this.code = code;         this.descr = descr;     }     @Override     public int getCode() {         return this.code;     }
      @Override     public String getDescription() {         return this.descr;     } }
  UserStatusConverter 代码如下:
 // 基于枚举的 Code 完成持久化处理 @Converter(autoApply = true) public class UserStatusConverter        extends CommonEnumAttributeConverter<UserStatus> {    public UserStatusConverter(){        super(UserStatus.values());    } }
 3.2.4.2. 上下文工厂 选择创建上下文工厂,弹出以下弹窗:
 
  
  点击“OK” 自动生成 CreateBasicUserContextFactory 如下:
 @Component @Slf4j public class CreateBasicUserContextFactory         extends AbstractSmartContextFactory<CreateBasicUserCommand, CreateBasicUserContext> {
      public CreateBasicUserContextFactory(){         super(CreateBasicUserCommand.class, CreateBasicUserContext.class);     }
      @Override     public CreateBasicUserContext create(CreateBasicUserCommand cmd) {         CreateBasicUserContext context = CreateBasicUserContext.apply(cmd);         return context;     } }
 3.2.4.3. 聚合根工厂 选择创建聚合根工厂,弹出以下弹窗:
 
  
  点击“确定”自动生成 BasicUserFactory 如下:
 @Component @Slf4j public class BasicUserFactory         extends AbstractSmartAggFactory<CreateBasicUserContext, BasicUser> {
      public BasicUserFactory(){         super(CreateBasicUserContext.class, BasicUser.class);     }
      @Override     public BasicUser create(CreateBasicUserContext context) {         return BasicUser.create(context);     } }
 3.2.4.4. 业务验证器 右键选择“创建业务验证器”,输入类名为:CreateBasicUserPhoneValidator,自动生成代码如下:
 @Component @Slf4j public class CreateBasicUserPhoneValidator     extends AbstractBusinessValidator<CreateBasicUserContext> {     public CreateBasicUserPhoneValidator(){         super(CreateBasicUserContext.class);     }     @Override     public void validate(CreateBasicUserContext context, ValidateErrorHandler validateErrorHandler) {         log.info("validate(context) {}", context);     } }
 3.2.4.5. 结果转换器 右键选择“创建结果转换器”,弹出如下对话框:
 
  
  自动生成代码如下:
 @Component @Slf4j public class BasicUserKeyConverter         extends AbstractSmartResultConverter<BasicUser, CreateBasicUserContext, BasicUserKey> {
      public BasicUserKeyConverter(){         super(BasicUser.class, CreateBasicUserContext.class, BasicUserKey.class);     }
      @Override     public BasicUserKey convert(BasicUser agg, CreateBasicUserContext context) {         // 添加转换代码         return null;     } }
 
  Github上标星60K的电商实战项目mall,全套 视频教程(2023最新版) 已更新完毕!全套教程约40小时,共113期,通过这套教程你可以拥有一个涵盖主流Java技术栈的完整项目经验,同时提高自己独立开发一个项目的能力,下面是项目的整体架构图,感兴趣的小伙伴可以点击链接 mall视频教程 加入学习。
 
  
  整套 视频教程 的内容还是非常完善的,涵盖了mall项目最佳学习路线、整体框架搭建、业务与技术实现全方位解析、线上Docker环境部署、微服务项目学习等内容,你也可以点击链接 mall视频教程 了解更多内容。
 推荐阅读- 69K Star!这是我见过最强的开源电商系统 !!
 - Github标星60K!一套完整的项目实战教程来了,主流Java技术一网打尽!
 - 看了我项目中购物车、订单、支付一整套设计,同事也开始悄悄模仿了...
 - 订单系统就该这么设计,稳的一批!
 - 支付系统就该这么设计,稳的一批!
 - 权限系统就该这么设计,稳的一批!
 
 
 
 
 
 
  上一篇:投行Jefferies迎来强援!前华兴资本高管开启新征程 下一篇:小红书引流方式,每天询单1000加 |