# AgileBoot DDD/CQRS 开发指南 - 新增表完整实现 ## 📖 文档概述 本文档详细说明了在 AgileBoot 框架中新增一张数据表后,如何按照 DDD/CQRS 架构规范写出完整的 `command`、`db`、`dto`、`model`、`query`、`ApplicationService` 等全套代码。 基于对 `agileboot-domain` 模块的深度分析,结合 `system/user`、`shop`、`cabinet` 等真实业务模块的代码实践,本指南提供了可直接使用的代码模板和最佳实践。 --- ## 🏗️ 目录结构概览 新增一张表后,在 `agileboot-domain/src/main/java/com/agileboot/domain/{module}/` 目录下需要创建以下结构: ``` {module}/ ├── command/ # 命令对象(写操作) │ ├── Add{Module}Command.java │ ├── Update{Module}Command.java │ ├── Delete{Module}Command.java │ └── ... ├── db/ # 数据层 │ ├── entity/ # 实体类 │ │ └── {Module}Entity.java │ ├── service/ # 服务层 │ │ ├── {Module}Service.java │ │ └── impl/ │ │ └── {Module}ServiceImpl.java │ ├── mapper/ # 数据访问 │ │ └── {Module}Mapper.java │ └── do/ # 数据对象(复杂查询) │ └── Search{Module}DO.java ├── dto/ # 数据传输对象 │ ├── {Module}DTO.java │ ├── {Module}DetailDTO.java │ └── {Module}ProfileDTO.java ├── model/ # 领域模型 │ ├── {Module}Model.java │ └── {Module}ModelFactory.java ├── query/ # 查询对象 │ └── Search{Module}Query.java └── {Module}ApplicationService.java # 应用服务层 ``` --- ## 📝 核心组件详解 ### 1. Entity(实体类) **位置**: `db/entity/{Module}Entity.java` **职责**: 数据库表的 Java 映射,继承 `BaseEntity` 获得通用字段 **关键特性**: - 继承 `BaseEntity` - 实现 `pkVal()` 返回主键 - 使用 MyBatis-Plus 注解:`@TableName`、`@TableId`、`@TableField` - 所有字段使用包装类型(Long, Integer),避免空指针 #### 代码示例 ```java package com.agileboot.domain.example.user.db; import com.agileboot.common.core.base.BaseEntity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import java.io.Serializable; import java.math.BigDecimal; import java.util.Date; import lombok.Data; import lombok.EqualsAndHashCode; /** * 用户信息表 * * @author your-name * @since 2025-01-01 */ @Data @EqualsAndHashCode(callSuper = true) @TableName("example_user") @ApiModel(value = "ExampleUserEntity对象", description = "用户信息表") public class ExampleUserEntity extends BaseEntity { private static final long serialVersionUID = 1L; @ApiModelProperty("用户ID") @TableId(value = "user_id", type = IdType.AUTO) private Long userId; @ApiModelProperty("用户姓名") @TableField("user_name") private String userName; @ApiModelProperty("年龄") @TableField("age") private Integer age; @ApiModelProperty("邮箱") @TableField("email") private String email; @ApiModelProperty("余额") @TableField("balance") private BigDecimal balance; @ApiModelProperty("状态(1正常 2停用)") @TableField("status") private Integer status; @ApiModelProperty("部门ID") @TableField("dept_id") private Long deptId; @ApiModelProperty("备注") @TableField("remark") private String remark; @Override public Serializable pkVal() { return this.userId; } } ``` **关键点**: - 类名: `{Module}Entity`,表名使用下划线命名 `{module}_{name}` - 主键字段: `{module}_id`,类型为 `Long`,注解 `@TableId(type = IdType.AUTO)` - 继承 `BaseEntity` 获得: `creatorId`, `createTime`, `updaterId`, `updateTime`, `deleted` --- ### 2. Service(服务层) **位置**: `db/service/{Module}Service.java` 和 `impl/{Module}ServiceImpl.java` **职责**: 封装数据库操作和简单业务逻辑,继承 MyBatis-Plus 的 `IService` #### 接口定义 ```java package com.agileboot.domain.example.user.db; import com.agileboot.common.core.page.AbstractPageQuery; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.IService; /** * 用户信息表 服务类 * * @author your-name * @since 2025-01-01 */ public interface ExampleUserService extends IService { /** * 检测邮箱是否唯一 * * @param email 邮箱 * @param userId 用户ID(更新时传入,排除自身) * @return 是否唯一 */ boolean isEmailUnique(String email, Long userId); /** * 检测用户名是否唯一 * * @param userName 用户名 * @return 是否唯一 */ boolean isUserNameUnique(String userName); /** * 获取用户的部门信息 * * @param userId 用户ID * @return 部门信息 */ Long getDeptIdOfUser(Long userId); /** * 根据条件分页查询用户列表 * * @param query 查询参数 * @return 用户信息集合 */ Page getUserList(AbstractPageQuery query); /** * 根据条件分页查询用户列表(带额外字段) * * @param query 查询参数 * @return 用户信息集合(含关联字段) */ Page getUserListWithJoin(AbstractPageQuery query); } ``` #### 实现类 ```java package com.agileboot.domain.example.user.db.impl; import cn.hutool.core.util.StrUtil; import com.agileboot.common.core.page.AbstractPageQuery; import com.agileboot.domain.example.user.db.ExampleUserEntity; import com.agileboot.domain.example.user.db.SearchUserDO; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import java.util.Objects; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; /** * 用户信息表 服务实现类 * * @author your-name * @since 2025-01-01 */ @Service @RequiredArgsConstructor public class ExampleUserServiceImpl extends ServiceImpl implements ExampleUserService { @Override public boolean isEmailUnique(String email, Long userId) { if (StrUtil.isEmpty(email)) { return true; } LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>() .eq(ExampleUserEntity::getEmail, email) .eq(ExampleUserEntity::getDeleted, 0); // 如果是更新操作,排除自身 if (userId != null) { wrapper.ne(ExampleUserEntity::getUserId, userId); } return !this.count(wrapper) > 0; } @Override public boolean isUserNameUnique(String userName) { if (StrUtil.isEmpty(userName)) { return true; } LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>() .eq(ExampleUserEntity::getUserName, userName) .eq(ExampleUserEntity::getDeleted, 0); return !this.count(wrapper) > 0; } @Override public Long getDeptIdOfUser(Long userId) { if (userId == null) { return null; } ExampleUserEntity entity = this.getById(userId); return entity != null ? entity.getDeptId() : null; } @Override public Page getUserList(AbstractPageQuery query) { return this.page(query.buildPage(), query.addQueryCondition()); } @Override public Page getUserListWithJoin(AbstractPageQuery query) { // 自定义 SQL 查询,返回关联数据 return this.getBaseMapper().selectUserListWithJoin(query.buildPage(), query); } } ``` **关键点**: - 接口继承 `IService` - 实现类继承 `ServiceImpl` - 简单业务逻辑在 Service 中处理 - 复杂业务逻辑交给 Model 处理 --- ### 3. Mapper(数据访问层) **位置**: `db/mapper/{Module}Mapper.java` **职责**: MyBatis 数据访问接口,通常使用 MyBatis-Plus 的 `BaseMapper` #### 代码示例 ```java package com.agileboot.domain.example.user.db; import com.agileboot.common.core.page.AbstractPageQuery; import com.agileboot.domain.example.user.db.SearchUserDO; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; /** * 用户信息表 Mapper 接口 * * @author your-name * @since 2025-01-01 */ @Mapper public interface ExampleUserMapper extends BaseMapper { /** * 分页查询用户列表(带关联数据) * * @param page 分页参数 * @param query 查询条件 * @return 用户列表 */ @Select({ "" }) Page selectUserListWithJoin( Page page, @Param("query") AbstractPageQuery query ); } ``` **关键点**: - 继承 `BaseMapper` - 复杂查询使用 `@Select` 注解或 XML 文件 - 使用 `#{}` 进行参数绑定,避免 SQL 注入 --- ### 4. DO(数据对象) **位置**: `db/do/Search{Module}DO.java` **职责**: 复杂查询返回的数据对象,包含关联表字段 #### 代码示例 ```java package com.agileboot.domain.example.user.db; import com.baomidou.mybatisplus.annotation.TableField; import java.math.BigDecimal; import java.util.Date; import lombok.Data; /** * 用户查询数据对象(包含关联字段) * * @author your-name * @since 2025-01-01 */ @Data public class SearchUserDO { private Long userId; private String userName; private Integer age; private String email; private BigDecimal balance; private Integer status; private Long deptId; @TableField("dept_name") private String deptName; @TableField("role_name") private String roleName; private Date createTime; private Date updateTime; private String remark; } ``` **关键点**: - 用于复杂查询,包含关联表字段 - 字段名使用数据库列名或 `as` 别名 - 不需要继承任何类 --- ### 5. Command(命令对象) **位置**: `command/Add{Module}Command.java`、`Update{Module}Command.java` **职责**: 封装写操作的输入参数,用于接收前端传入的数据 #### 添加命令 ```java package com.agileboot.domain.example.user.command; import com.agileboot.common.annotation.ExcelColumn; import lombok.Data; /** * 添加用户命令 * * @author your-name * @since 2025-01-01 */ @Data public class AddUserCommand { @ExcelColumn(name = "用户姓名") private String userName; @ExcelColumn(name = "年龄") private Integer age; @ExcelColumn(name = "邮箱") private String email; @ExcelColumn(name = "余额") private String balance; // 使用 String 接收,前端传入 @ExcelColumn(name = "状态") private Integer status; @ExcelColumn(name = "部门ID") private Long deptId; @ExcelColumn(name = "备注") private String remark; } ``` #### 更新命令 ```java package com.agileboot.domain.example.user.command; import com.agileboot.common.annotation.ExcelColumn; import lombok.Data; /** * 更新用户命令 * * @author your-name * @since 2025-01-01 */ @Data public class UpdateUserCommand extends AddUserCommand { @ExcelColumn(name = "用户ID") private Long userId; } ``` **关键点**: - 纯 POJO 类,仅用于数据传输 - 使用 `@ExcelColumn` 注解支持 Excel 导入导出 - 命令对象通常继承或包含基础字段 - 字段名与前端保持一致 --- ### 6. Query(查询对象) **位置**: `query/Search{Module}Query.java` **职责**: 封装查询条件和分页参数,构建 MyBatis 查询条件 #### 代码示例 ```java package com.agileboot.domain.example.user.query; import cn.hutool.core.util.StrUtil; import com.agileboot.common.core.page.AbstractPageQuery; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import lombok.Data; import lombok.EqualsAndHashCode; /** * 用户查询条件 * * @author your-name * @since 2025-01-01 */ @EqualsAndHashCode(callSuper = true) @Data public class SearchUserQuery extends AbstractPageQuery { private Long userId; private String userName; private String email; private Integer status; private Long deptId; private Integer minAge; private Integer maxAge; @Override public QueryWrapper addQueryCondition() { QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.like(StrUtil.isNotEmpty(userName), "user_name", userName) .like(StrUtil.isNotEmpty(email), "email", email) .eq(userId != null, "user_id", userId) .eq(status != null, "status", status) .eq(deptId != null, "dept_id", deptId) .eq("deleted", 0) .between(minAge != null && maxAge != null, "age", minAge, maxAge); // 设置时间范围排序字段 this.timeRangeColumn = "create_time"; return queryWrapper; } } ``` **关键点**: - 继承 `AbstractPageQuery` - 实现 `addQueryCondition()` 方法构建查询条件 - 使用链式调用构建查询条件 - 非空判断:`StrUtil.isNotEmpty()` 和 `!= null` - 设置默认排序字段 --- ### 7. DTO(数据传输对象) **位置**: `dto/{Module}DTO.java`、`{Module}DetailDTO.java`、`{Module}ProfileDTO.java` **职责**: 向前端传输数据,支持缓存集成和字段转换 #### 基础 DTO ```java package com.agileboot.domain.example.user.dto; import cn.hutool.core.bean.BeanUtil; import com.agileboot.common.annotation.ExcelColumn; import com.agileboot.common.annotation.ExcelSheet; import com.agileboot.domain.common.cache.CacheCenter; import com.agileboot.domain.example.user.db.ExampleUserEntity; import com.agileboot.domain.example.user.db.SearchUserDO; import java.math.BigDecimal; import java.util.Date; import lombok.Data; /** * 用户DTO * * @author your-name * @since 2025-01-01 */ @ExcelSheet(name = "用户列表") @Data public class UserDTO { public UserDTO(ExampleUserEntity entity) { if (entity != null) { BeanUtil.copyProperties(entity, this); // 从缓存中获取关联数据 if (entity.getDeptId() != null) { // 示例:获取部门名称(如果缓存中有) // this.deptName = CacheCenter.deptCache.get(entity.getDeptId() + "")...; } } } public UserDTO(SearchUserDO entity) { if (entity != null) { BeanUtil.copyProperties(entity, this); } } @ExcelColumn(name = "用户ID") private Long userId; @ExcelColumn(name = "用户姓名") private String userName; @ExcelColumn(name = "年龄") private Integer age; @ExcelColumn(name = "邮箱") private String email; @ExcelColumn(name = "余额") private BigDecimal balance; @ExcelColumn(name = "状态") private String status; // 可以转换为文字描述 @ExcelColumn(name = "部门ID") private Long deptId; @ExcelColumn(name = "部门名称") private String deptName; @ExcelColumn(name = "创建时间") private Date createTime; @ExcelColumn(name = "备注") private String remark; } ``` #### 详情 DTO(包含更多选项数据) ```java package com.agileboot.domain.example.user.dto; import com.agileboot.domain.example.user.db.ExampleUserEntity; import com.agileboot.domain.system.dept.dto.DeptDTO; import com.agileboot.domain.system.role.dto.RoleDTO; import java.util.List; import lombok.Data; /** * 用户详情DTO(包含选项数据) * * @author your-name * @since 2025-01-01 */ @Data public class UserDetailDTO { private UserDTO user; private List deptOptions; private List roleOptions; } ``` **关键点**: - 支持从 Entity 或 DO 转换 - 使用 `BeanUtil.copyProperties()` 进行属性复制 - 可以集成多级缓存获取关联数据 - 支持 Excel 导入导出(`@ExcelColumn`) - 可包含选项数据(如部门列表、角色列表) --- ### 8. Model(领域模型) **位置**: `model/{Module}Model.java` **职责**: 领域模型,封装核心业务逻辑,包括校验、状态转换等 #### 代码示例 ```java package com.agileboot.domain.example.user.model; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.util.StrUtil; import com.agileboot.common.exception.ApiException; import com.agileboot.common.exception.error.ErrorCode; import com.agileboot.domain.example.user.command.AddUserCommand; import com.agileboot.domain.example.user.command.UpdateUserCommand; import com.agileboot.domain.example.user.db.ExampleUserEntity; import com.agileboot.domain.example.user.db.ExampleUserService; import java.math.BigDecimal; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; /** * 用户领域模型 * * @author your-name * @since 2025-01-01 */ @EqualsAndHashCode(callSuper = true) @Data @NoArgsConstructor public class UserModel extends ExampleUserEntity { private ExampleUserService userService; public UserModel(ExampleUserEntity entity, ExampleUserService userService) { if (entity != null) { BeanUtil.copyProperties(entity, this); } this.userService = userService; } public UserModel(ExampleUserService userService) { this.userService = userService; } /** * 加载添加用户命令 */ public void loadAddUserCommand(AddUserCommand command) { if (command != null) { BeanUtil.copyProperties(command, this, "userId"); // 转换余额字段 if (StrUtil.isNotEmpty(command.getBalance())) { try { this.setBalance(new BigDecimal(command.getBalance())); } catch (NumberFormatException e) { throw new ApiException(ErrorCode.Business.COMMON_BAD_REQUEST, "余额格式不正确"); } } // 设置默认值 if (this.getStatus() == null) { this.setStatus(1); // 默认正常状态 } } } /** * 加载更新用户命令 */ public void loadUpdateUserCommand(UpdateUserCommand command) { if (command != null) { loadAddUserCommand(command); } } /** * 校验用户名唯一性 */ public void checkUserNameIsUnique() { if (!userService.isUserNameUnique(getUserName())) { throw new ApiException(ErrorCode.Business.USER_NAME_IS_NOT_UNIQUE); } } /** * 校验邮箱唯一性 */ public void checkEmailIsUnique() { if (!userService.isEmailUnique(getEmail(), getUserId())) { throw new ApiException(ErrorCode.Business.USER_EMAIL_IS_NOT_UNIQUE); } } /** * 校验关联数据是否存在 */ public void checkFieldRelatedEntityExist() { // 示例:校验部门是否存在 if (getDeptId() != null) { // 可以通过其他 Service 校验部门是否存在 // Example: deptService.getById(getDeptId()); } } /** * 校验是否可以删除 */ public void checkCanBeDelete() { // 业务逻辑:例如检查用户是否有未完成的订单 // if (hasUnfinishedOrders()) { // throw new ApiException(ErrorCode.Business.COMMON_BAD_REQUEST, "用户存在未完成的订单,无法删除"); // } // 示例:检查是否为系统管理员 if (this.getIsAdmin() != null && this.getIsAdmin()) { throw new ApiException(ErrorCode.Business.COMMON_BAD_REQUEST, "系统管理员不能删除"); } } /** * 设置密码(加密) */ public void setEncryptedPassword(String rawPassword) { // 示例:加密密码 // this.setPassword(PasswordUtils.encrypt(rawPassword)); } /** * 校验年龄是否合法 */ public void checkAgeIsValid() { if (getAge() != null && (getAge() < 0 || getAge() > 150)) { throw new ApiException(ErrorCode.Business.COMMON_BAD_REQUEST, "年龄必须在0-150之间"); } } /** * 预保存校验 */ public void validateBeforeSave() { if (StrUtil.isEmpty(getUserName())) { throw new ApiException(ErrorCode.Business.COMMON_BAD_REQUEST, "用户名不能为空"); } checkUserNameIsUnique(); checkEmailIsUnique(); checkFieldRelatedEntityExist(); checkAgeIsValid(); } } ``` **关键点**: - 继承实体类,获得所有字段 - 注入 Service 用于数据操作 - 包含 `load*Command()` 方法加载命令数据 - 包含 `check*()` 方法进行业务校验 - 包含业务逻辑处理方法 - 在 Model 中封装所有业务规则 --- ### 9. ModelFactory(模型工厂) **位置**: `model/{Module}ModelFactory.java` **职责**: 统一创建 Model 实例,管理依赖注入和错误处理 #### 代码示例 ```java package com.agileboot.domain.example.user.model; import com.agileboot.common.exception.ApiException; import com.agileboot.common.exception.error.ErrorCode; import com.agileboot.domain.example.user.db.ExampleUserEntity; import com.agileboot.domain.example.user.db.ExampleUserService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; /** * 用户模型工厂 * * @author your-name * @since 2025-01-01 */ @Component @RequiredArgsConstructor public class UserModelFactory { private final ExampleUserService userService; /** * 根据ID加载用户模型 * * @param userId 用户ID * @return 用户模型 */ public UserModel loadById(Long userId) { ExampleUserEntity entity = userService.getById(userId); if (entity == null) { throw new ApiException(ErrorCode.Business.COMMON_OBJECT_NOT_FOUND, userId, "用户"); } return new UserModel(entity, userService); } /** * 创建新用户模型 * * @return 用户模型 */ public UserModel create() { return new UserModel(userService); } } ``` **关键点**: - 使用 `@Component` 注解注入 Spring 容器 - 使用 `@RequiredArgsConstructor` 自动注入依赖 - `loadById()` 检查数据是否存在,不存在则抛异常 - `create()` 创建空模型,用于新增操作 --- ### 10. ApplicationService(应用服务) **位置**: `{Module}ApplicationService.java` **职责**: 事务脚本层,协调 Model 和 Service,处理业务流程编排 #### 代码示例 ```java package com.agileboot.domain.example.user; import cn.hutool.core.convert.Convert; import com.agileboot.common.core.page.PageDTO; import com.agileboot.domain.common.command.BulkOperationCommand; import com.agileboot.domain.example.user.command.AddUserCommand; import com.agileboot.domain.example.user.command.UpdateUserCommand; import com.agileboot.domain.example.user.db.SearchUserDO; import com.agileboot.domain.example.user.dto.UserDTO; import com.agileboot.domain.example.user.dto.UserDetailDTO; import com.agileboot.domain.example.user.model.UserModel; import com.agileboot.domain.example.user.model.UserModelFactory; import com.agileboot.domain.example.user.query.SearchUserQuery; import com.agileboot.domain.system.dept.db.SysDeptService; import com.agileboot.domain.system.dept.dto.DeptDTO; import com.agileboot.domain.example.user.db.ExampleUserEntity; import com.agileboot.domain.example.user.db.ExampleUserService; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import java.util.List; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; /** * 用户应用服务 * * @author your-name * @since 2025-01-01 */ @Service @RequiredArgsConstructor public class UserApplicationService { private final ExampleUserService userService; private final SysDeptService deptService; private final UserModelFactory userModelFactory; /** * 获取用户列表(分页) */ public PageDTO getUserList(SearchUserQuery query) { Page userPage = userService.getUserListWithJoin(query); List userDTOList = userPage.getRecords() .stream() .map(UserDTO::new) .collect(Collectors.toList()); return new PageDTO<>(userDTOList, userPage.getTotal()); } /** * 获取用户详情 */ public UserDetailDTO getUserDetailInfo(Long userId) { ExampleUserEntity userEntity = userService.getById(userId); UserDetailDTO detailDTO = new UserDetailDTO(); detailDTO.setUser(new UserDTO(userEntity)); // 获取部门选项 List deptOptions = deptService.list() .stream() .map(DeptDTO::new) .collect(Collectors.toList()); detailDTO.setDeptOptions(deptOptions); return detailDTO; } /** * 添加用户 */ @Transactional(rollbackFor = Exception.class) public void addUser(AddUserCommand command) { UserModel model = userModelFactory.create(); model.loadAddUserCommand(command); // 业务校验 model.validateBeforeSave(); // 保存 model.insert(); } /** * 更新用户 */ @Transactional(rollbackFor = Exception.class) public void updateUser(UpdateUserCommand command) { UserModel model = userModelFactory.loadById(command.getUserId()); model.loadUpdateUserCommand(command); // 业务校验 model.checkEmailIsUnique(); model.checkFieldRelatedEntityExist(); model.checkAgeIsValid(); // 更新 model.updateById(); } /** * 删除用户(单个) */ @Transactional(rollbackFor = Exception.class) public void deleteUser(Long userId) { UserModel model = userModelFactory.loadById(userId); // 业务校验 model.checkCanBeDelete(); // 软删除 model.deleteById(); } /** * 批量删除用户 */ @Transactional(rollbackFor = Exception.class) public void deleteUsers(BulkOperationCommand command) { for (Long userId : command.getIds()) { UserModel model = userModelFactory.loadById(userId); model.checkCanBeDelete(); model.deleteById(); } } /** * 更改用户状态 */ @Transactional(rollbackFor = Exception.class) public void changeUserStatus(Long userId, Integer status) { UserModel model = userModelFactory.loadById(userId); model.setStatus(status); model.updateById(); } /** * 重置用户密码 */ @Transactional(rollbackFor = Exception.class) public void resetPassword(Long userId, String newPassword) { UserModel model = userModelFactory.loadById(userId); model.setEncryptedPassword(newPassword); model.updateById(); } } ``` **关键点**: - 使用 `@Service` 注解 - 使用 `@RequiredArgsConstructor` 注入依赖 - 写操作使用 `@Transactional` 注解保证事务 - 协调 Model 和 Service - 返回 DTO 给 Controller - 包含完整的 CRUD 操作 --- ## 🔄 CQRS 流程 ### Command(写操作)流程 ``` Controller → Command → ApplicationService → Model → 业务校验 → 保存 ``` **示例**: ```java // 1. Controller 接收参数,封装为 Command AddUserCommand command = new AddUserCommand(); // ... 设置参数 // 2. ApplicationService 处理 userApplicationService.addUser(command); // 3. ApplicationService 创建 Model 并加载 Command UserModel model = userModelFactory.create(); model.loadAddUserCommand(command); // 4. Model 进行业务校验 model.validateBeforeSave(); // 5. Model 执行保存操作 model.insert(); ``` ### Query(读操作)流程 ``` Controller → Query → Service → Mapper → DTO ``` **示例**: ```java // 1. Controller 接收查询参数,封装为 Query SearchUserQuery query = new SearchUserQuery<>(); // ... 设置查询条件 // 2. ApplicationService 处理 PageDTO result = userApplicationService.getUserList(query); // 3. Service 执行查询 Page userPage = userService.getUserListWithJoin(query); // 4. 转换为 DTO List userDTOList = userPage.getRecords() .stream() .map(UserDTO::new) .collect(Collectors.toList()); return new PageDTO<>(userDTOList, userPage.getTotal()); ``` --- ## 📌 最佳实践 ### 1. 命名规范 | 类型 | 命名规则 | 示例 | |------|----------|------| | Entity | `{Module}Entity` | `SysUserEntity` | | Service 接口 | `{Module}Service` | `SysUserService` | | Service 实现 | `{Module}ServiceImpl` | `SysUserServiceImpl` | | Mapper | `{Module}Mapper` | `SysUserMapper` | | Model | `{Module}Model` | `UserModel` | | ModelFactory | `{Module}ModelFactory` | `UserModelFactory` | | Command | `{Operation}{Module}Command` | `AddUserCommand` | | Query | `Search{Module}Query` | `SearchUserQuery` | | DTO | `{Module}DTO` | `UserDTO` | | ApplicationService | `{Module}ApplicationService` | `UserApplicationService` | ### 2. 字段命名规范 - **Entity**: 使用下划线命名,与数据库保持一致 - `user_id`, `user_name`, `create_time` - **Command/Query/DTO**: 使用驼峰命名,与前端保持一致 - `userId`, `userName`, `createTime` ### 3. 类型选择 - **主键**: 使用 `Long`(包装类型) - **状态/数量**: 使用 `Integer`(包装类型) - **金额**: 使用 `BigDecimal`(避免精度问题) - **字符串**: 避免基本类型,使用 `String` - **时间**: 使用 `Date` 或 `LocalDateTime` ### 4. 空值处理 - 始终使用包装类型,避免 NPE - 使用 `StrUtil.isNotEmpty()` 判断字符串 - 使用 `!= null` 判断对象和数字 - 使用 MyBatis-Plus 的链式调用:`eq(condition, "column", value)` ### 5. 业务校验位置 - **唯一性校验**: Model 层 - **关联数据存在性校验**: Model 层 - **字段格式校验**: Model 层 - **业务规则校验**: Model 层 - **数据库约束**: Entity 层 ### 6. 缓存集成 DTO 中可以从缓存获取关联数据: ```java public UserDTO(ExampleUserEntity entity) { if (entity != null) { BeanUtil.copyProperties(entity, this); // 从缓存获取部门名称 if (entity.getDeptId() != null) { SysDeptEntity dept = CacheCenter.deptCache.get(entity.getDeptId() + ""); this.deptName = dept != null ? dept.getDeptName() : ""; } } } ``` ### 7. 事务管理 - ApplicationService 负责事务管理 - 使用 `@Transactional(rollbackFor = Exception.class)` - 写操作必须开启事务 - 读操作可以不加事务(但建议加 `@Transactional(readOnly = true)`) ### 8. 异常处理 - 业务异常使用 `ApiException` - 错误码使用 `ErrorCode` 枚举 - Model 层抛出异常,ApplicationService 不捕获 - Controller 层统一处理异常 ### 9. 代码复用 - 公共方法抽取到工具类 - 通用 DTO/Entity 放在 `common` 模块 - 公共 Service 方法抽取到基类 - 使用组合而非继承 --- ## 🛠️ 开发步骤清单 新增一张表后的完整开发流程: ### 1. 数据库层面 - [ ] 创建数据库表(`sql/YYYYMMDD_feature_name.sql`) - [ ] 添加表注释和字段注释 - [ ] 创建必要的索引 ### 2. Entity 层 - [ ] 创建 `{Module}Entity.java`,继承 `BaseEntity` - [ ] 配置 `@TableName`、`@TableId`、`@TableField` 注解 - [ ] 实现 `pkVal()` 方法 ### 3. Mapper 层 - [ ] 创建 `{Module}Mapper.java`,继承 `BaseMapper` - [ ] 添加自定义查询方法(如需要) ### 4. Service 层 - [ ] 创建 `{Module}Service.java` 接口 - [ ] 创建 `{Module}ServiceImpl.java` 实现类 - [ ] 继承 `ServiceImpl` - [ ] 实现核心业务方法 ### 5. Command 层 - [ ] 创建 `Add{Module}Command.java` - [ ] 创建 `Update{Module}Command.java`(可选:继承基础 Command) - [ ] 创建其他 Command(如 `Delete{Module}Command.java`) ### 6. Query 层 - [ ] 创建 `Search{Module}Query.java` - [ ] 继承 `AbstractPageQuery` - [ ] 实现 `addQueryCondition()` 方法 ### 7. DTO 层 - [ ] 创建 `{Module}DTO.java` - [ ] 添加构造函数(从 Entity/DO 转换) - [ ] 添加 `@ExcelColumn` 注解(如果需要导出) - [ ] 集成缓存获取关联数据(如果需要) ### 8. Model 层 - [ ] 创建 `{Module}Model.java`,继承 Entity - [ ] 添加 `load*Command()` 方法 - [ ] 添加 `check*()` 业务校验方法 - [ ] 添加业务逻辑处理方法 ### 9. ModelFactory 层 - [ ] 创建 `{Module}ModelFactory.java` - [ ] 使用 `@Component` 注解 - [ ] 添加 `loadById()` 方法 - [ ] 添加 `create()` 方法 ### 10. ApplicationService 层 - [ ] 创建 `{Module}ApplicationService.java` - [ ] 使用 `@Service` 注解 - [ ] 注入 Service 和 ModelFactory - [ ] 实现 CRUD 方法 - [ ] 添加 `@Transactional` 注解 ### 11. Controller 层(在 agileboot-admin 中) - [ ] 创建 Controller - [ ] 注入 ApplicationService - [ ] 定义 RESTful API - [ ] 添加 `@Operation` 注解 ### 12. 测试 - [ ] 编写单元测试(Service、Model) - [ ] 编写集成测试 - [ ] 测试 CRUD 操作 - [ ] 测试业务校验 - [ ] 测试缓存(如果使用) ### 13. 文档 - [ ] 更新 API 文档 - [ ] 补充业务说明 - [ ] 更新数据库文档 --- ## 📚 参考资料 ### 代码结构参考 - **System 用户管理**: `agileboot-domain/src/main/java/com/agileboot/domain/system/user/` - **Shop 电商模块**: `agileboot-domain/src/main/java/com/agileboot/domain/shop/` - **Cabinet 智能柜**: `agileboot-domain/src/main/java/com/agileboot/domain/cabinet/` ### 核心类参考 - **BaseEntity**: `com.agileboot.common.core.base.BaseEntity` - **AbstractPageQuery**: `com.agileboot.common.core.page.AbstractPageQuery` - **PageDTO**: `com.agileboot.common.core.page.PageDTO` - **ApiException**: `com.agileboot.common.exception.ApiException` - **ErrorCode**: `com.agileboot.common.exception.error.ErrorCode` - **CacheCenter**: `com.agileboot.domain.common.cache.CacheCenter` --- ## 🎯 总结 AgileBoot 的 DDD/CQRS 架构通过清晰的分层和职责分离,为企业级应用开发提供了强有力的支撑: 1. **Entity**: 数据库映射,继承 BaseEntity 2. **Service**: 数据库操作和简单业务逻辑 3. **Command**: 写操作参数封装 4. **Query**: 读操作查询条件 5. **DTO**: 前端数据传输,支持缓存 6. **Model**: 领域模型,封装核心业务逻辑 7. **ModelFactory**: 统一创建 Model 8. **ApplicationService**: 事务脚本层,协调各组件 遵循本指南的规范,可以快速构建符合 DDD/CQRS 架构的高质量代码。 --- **文档版本**: v1.0 **最后更新**: 2025-01-01 **作者**: AgileBoot Team