熬夜开发Idea插件:知识铺 -- 知识铺
DDD 实践指南
背景
领域驱动设计(DDD)是一种软件开发方法论,它通过将复杂业务逻辑映射到软件模型中来提高开发效率和软件质量。然而,DDD的入门门槛较高,其内部包含许多抽象且难以理解的概念,例如实体(Entity)、值对象(Value Object)、领域服务(Domain Service)、领域事件(Domain Event)、聚合根(Aggregate Root)、工厂(Factory)、仓库(Repository)以及应用服务(Application Service)等。这些概念对于初学者来说可能难以掌握,但对于坚持学习和深入理解的人来说,它们是构建科学且高度结构化解决方案的关键。
挑战与困惑
- 概念理解:DDD 包含的复杂概念对于初学者来说是一个挑战。- 落地难度:即使理解了这些概念,将它们应用到实际项目中也是一个难题。- 团队一致性:确保整个团队对这些概念有统一的理解并步调一致地应用它们,更是难上加难。
解决方案
- 结构化:DDD 和 CQRS 提供了高度结构化的解决方案,清晰定义了组件的边界和职责。- 标准化:在结构化的基础上,每个组件都遵循相同的设计规则,实现标准化。- 模板化:标准化带来的重复逻辑可以通过模板化来减少手动编码的工作量。
目的
- 降低学习成本:简化概念,使初级开发者更容易理解和使用 DDD。- 统一规范:确保团队内部的业务流程和组件设计保持一致性。- 提升效率:通过自动化重复逻辑,提高开发效率。
功能介绍
Maven 脚手架
Maven 脚手架是用于快速构建符合公司规范的项目的工具。它通过以下命令实现:
shellmvn archetype:generate -Dfilter=<groupId>:<artifactId>:<version>
这个命令可以根据预定义的模板快速生成项目结构,降低新项目构建的成本,并确保项目结构的一致性。
使用指南
- 选择模板:根据项目需求选择合适的 Maven 模板。2. 执行命令:在终端或命令行界面执行上述 Maven 命令。3. 配置项目:根据生成的项目结构进行必要的配置和调整。 通过这种方式,开发者可以快速开始新项目的开发,同时确保项目遵循既定的最佳实践和标准。
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 构建工具命令行解析
概述本命令行是 Maven 构建工具的一部分,用于通过 archetype 插件生成项目骨架。该命令执行的是 generate
目标,创建一个基于预定义模板的项目结构实例。
参数详解以下是命令行中各个参数的具体含义:
-DarchetypeGroupId=com.geekhalo.lego
:指定使用原型的组织标识符,即 Maven 组织ID。此ID是自定义的,用于标识提供项目骨架模板的组织。-DarchetypeArtifactId=services-archetype
:指定要使用的原型工件ID,即在指定组织下用于生成新项目的模板名称。-DarchetypeVersion=0.1.39-plugin_demo-SNAPSHOT
:设置所用原型的版本号,此处为快照版本,意味着该版本可能在开发中频繁更新。-DgroupId=com.geekhalo
:为新生成的项目设置组织标识符,即新项目的 Maven 组织ID。-DartifactId=user
:设置新项目的工件ID,即项目名称。-Dversion=0.1.39-plugin_demo-SNAPSHOT
:设置新项目的初始版本号,与原型版本一致,使用快照版本。
命令作用执行该命令后,Maven 将在本地生成一个新的项目结构,该项目基于 com.geekhalo.lego
组织下的 services-archetype
模板。新项目初始化时的项目信息包括:- 组织ID:groupId=com.geekhalo
- 项目名:artifactId=user
- 初始版本:version=0.1.39-plugin_demo-SNAPSHOT
执行结果命令执行完成后,用户将看到新增的项目模块,该模块符合公司规范,名为 user
。
注意事项- 请确保 Maven 环境已正确配置。- 快照版本可能不稳定,建议关注更新。- 项目生成后,根据需要进行进一步的配置和开发。
用户服务项目结构概览
用户服务项目采用模块化设计,分为以下几个核心模块:
user-domain
: 核心领域模型和逻辑,是六边形架构的核心层,独立于外部系统和技术。user-infrastructure
: 包含具体实现细节和技术栈代码,如数据库访问和消息队列。user-app
: 应用逻辑实现,依赖其他模块完成业务功能。user-api
: 定义服务接口,包括RPC和消息服务契约。user-feign-service
: 定义Feign服务接口,供其他微服务调用。user-feign-client
: 提供SDK,简化其他服务对用户服务的调用。user-bootstrap
: 引导程序,包含启动类和配置文件。 在项目根目录下,有两个关键文件:pom.xml
: Maven配置文件,管理子模块依赖和构建。README.md
: 项目文档,包含项目介绍、运行和开发指南。
自定义Idea插件开发
3.2.1 创建聚合根和视图模型
聚合根是领域驱动设计(DDD)中的关键概念,作为业务逻辑的最小单元。开发过程中,聚合根是开发的核心。 Idea插件可以集成开发规范,降低开发成本,提高规范落地效率。
操作步骤:创建聚合根“BasicUser”
- 在
user-domain
模块下的basic
包右键。2. 选择lego
菜单中的“创建聚合根”功能。 聚合根“BasicUser”将用于存储用户基本信息,是日常开发中频繁操作的对象。
image
在弹出的对话框中填入 聚合根类名为“BasicUser”,其他保存默认,详见:
image
点击左上角的 View,切换到视图模型配置:
image
点击 “OK” 按钮,观察项目变化:
Domain 模块:
image
Infrastructure 模块:
image
App 模块:
在软件开发中,创建一个高效的聚合根是至关重要的。聚合根是领域驱动设计中的核心概念,它封装了业务逻辑和数据。本文将介绍如何通过自动化插件创建一个名为 BasicUser 的聚合根,从而节省开发时间并提高效率。
聚合根的自动化创建
自动化插件可以帮助我们快速生成所需的文件,这些文件在传统手工操作中会耗费大量时间。通过这种方式,我们可以专注于业务逻辑的实现,而不是文件的创建和管理。
CQRS 架构设计
CQRS(Command Query Responsibility Segregation)是一种将命令和查询分离的架构模式,它适用于各种复杂度的项目。
1. 简单场景
- 快速开发:在项目的初期,业务逻辑相对简单,需要快速开发和迭代。- 共享存储的 CQRS:在这个阶段,推荐使用共享存储的 CQRS 架构,以简化开发流程。
2. 复杂场景
- 架构升级:随着业务逻辑的增加,项目的复杂性也会随之增加。此时,需要在不破坏现有架构的前提下,快速升级架构。- 灵活应对:CQRS 架构允许我们在不影响整体设计的情况下,对系统进行灵活的扩展和升级。
设计原则
- 简单性:在设计初期,保持设计的简单性,以便于快速开发和理解。- 可扩展性:随着项目的发展,设计应具备良好的可扩展性,以适应不断变化的业务需求。
结构化内容
以下是 BasicUser 聚合根的创建步骤和考虑因素,以确保项目的高效和可维护性:
- 定义聚合根:明确 BasicUser 聚合根的职责和边界。2. 实现命令处理:为聚合根实现命令处理逻辑,以响应业务操作。3. 实现查询处理:提供查询逻辑,以支持对聚合根状态的读取。4. 数据存储:选择合适的数据存储方案,以支持命令和查询的执行。5. 测试:编写测试用例,确保聚合根的行为符合预期。 通过遵循上述步骤,我们可以创建一个既简单又可扩展的 BasicUser 聚合根,满足不同阶段的业务需求。
image
如果写操作和读操作两者差距巨大,推荐使用标准的 CQRS 架构,具体如下:
在采用领域驱动设计(DDD)构建微服务架构时,我们通常需要定义清晰的业务模型和操作流程。以下是对用户服务的领域模型和流程的介绍:
领域模型定义
- BasicUser:作为用户服务的聚合根,它包含了用户的属性信息和业务逻辑。聚合根是领域模型中的核心,负责维护数据的一致性和完整性。
- AbstractBasicUserEvent:定义了一个抽象事件类,用于表示用户服务中的关键事件。这些事件可以触发状态变化或其他业务行为。
- BasicUserCommandRepository:命令存储库,负责处理用户的创建、更新和删除等命令操作,并将这些操作持久化。
- JpaBasedBasicUserCommandRepository:命令存储库的具体实现,使用JPA操作数据库,让开发者可以专注于业务逻辑。
- BasicUserCommandApplication:命令应用服务,封装了用户服务的业务逻辑,协调领域对象和存储库的工作。
读流程设计
- BasicUserView:查询侧的视图模型,包含用户基本信息,不包含业务逻辑,用于快速响应查询请求。
- BasicUserQueryRepository:查询存储库,负责处理对用户视图的查询操作,支持从数据库或缓存中获取数据。
- JpaBasedBasicUserQueryRepository:查询存储库的具体实现,使用JPA操作数据库。
- BasicUserQueryApplication:查询应用服务,封装查询逻辑,协调视图模型和存储库的工作。
聚合根方法创建
在聚合根BasicUser
类上,可以通过IDEA插件的lego
功能快速生成业务方法。这可以提高开发效率,确保业务逻辑的封装和一致性。
image
在弹窗中填入方法名为:create,如图所示:
image
点击“OK”按钮,会有如下变化:
image
image
系统自动生成信息整理
1. 创建目录结构在系统中新创建的create
目录,包含了流程中的核心组件。
- CreateBasicUserCommand: 这是一个创建用户的命令对象,它包含了创建用户所需的所有参数和信息。客户端发送此命令对象到用户服务以创建新用户。- CreateBasicUserContext: 这是一个上下文对象,包含了创建用户所需的环境信息和其他辅助信息,有助于操作流程的数据维护和共享。- BasicUserCreatedEvent: 表示用户已被成功创建的事件对象。用户服务在接收到
CreateBasicUserCommand
命令并创建新用户后发布此事件。
3. BasicUser新增create业务方法- 静态create方法: 使用Context
对象创建BasicUser
。- 实例init方法: 对BasicUser
进行核心状态设置,创建并发布领域事件。
5. BasicUserCommandApplication新增create方法- 输入CreateBasicUserCommand
对象,协调内部组件,完成创建流程。
注意: 开发人员无需编写应用服务的代码,框架将自动生成
Proxy
类来完成全部流程。开发人员应专注于create
目录下的各种组件,组件的协作集成由lego框架自动完成。
3.2.3. 创建查询方法在BasicUserQueryApplication
中,通过右键选择创建“创建查询方法”,将弹出对话框以供进一步操作。
image
我们选择分页查询,确定后,自动生成:
@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;
}
在软件开发中,实现高效的数据检索和处理是一项重要的任务。下面将介绍一种通过注解和特定类型字段来声明查询能力的方法,以及框架对核心组件的支持。
声明查询能力
- 使用注解过滤字段 通过
@FieldEqualTo
注解,可以指定某个字段用于与指定的值进行匹配过滤,例如status
字段。这允许开发者在查询时对数据进行条件筛选。 - 分页信息 使用
Pageable
类型字段,可以指定查询的页码和每页的数据量。这使得开发者能够实现快速的数据分页,便于管理和展示大量数据。 - 数据排序
Sort
类型字段用于提供排序信息,允许开发者根据需要对数据进行排序,提高数据检索的效率和准确性。
框架核心组件支持
除了生成骨架代码,框架还提供了对以下核心组件的支持:
- 通用枚举 当需要创建枚举时,框架提供了一个对话框,允许开发者根据需求定义枚举类型。枚举是一种固定数量的值的集合,常用于表示一组常量。
操作步骤
- 声明查询能力时,首先使用
@FieldEqualTo
注解来指定需要过滤的字段。- 接着,通过Pageable
字段设置查询的分页信息,包括页码和每页大小。- 使用Sort
字段定义数据的排序规则。- 在创建枚举时,利用框架提供的对话框,根据具体需求定义枚举值。 通过上述方法,开发者可以更加灵活和高效地进行数据查询和处理。
image
点击确定,自动生成枚举和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. 上下文工厂
选择创建上下文工厂,弹出以下弹窗:
image
点击“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. 聚合根工厂
选择创建聚合根工厂,弹出以下弹窗:
image
点击“确定”自动生成 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. 结果转换器
右键选择“创建结果转换器”,弹出如下对话框:
image
自动生成代码如下:
@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;
}
}
4. 工程&源码
最后给出工程地址:https://gitee.com/litao851025/lego
- 原文作者:知识铺
- 原文链接:https://index.zshipu.com/geek001/post/20240730/%E7%86%AC%E5%A4%9C%E5%BC%80%E5%8F%91Idea%E6%8F%92%E4%BB%B6--%E7%9F%A5%E8%AF%86%E9%93%BA/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。
- 免责声明:本页面内容均来源于站内编辑发布,部分信息来源互联网,并不意味着本站赞同其观点或者证实其内容的真实性,如涉及版权等问题,请立即联系客服进行更改或删除,保证您的合法权益。转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。也可以邮件至 sblig@126.com