# Other 模块 DDD 架构说明文档 ## 一、模块概览 Other 服务是一个综合性的业务模块,包含用户、房间、游戏、活动、家族、礼物、任务等多个子领域。采用标准的 DDD(领域驱动设计)+ CQRS 架构模式。 ### 模块列表 ``` rc-service-other/ ├── other-domain # 领域层(核心业务实体) ├── other-infrastructure # 基础设施层(数据访问、外部服务) ├── other-client # 客户端接口层(DTO、Service接口) ├── other-application # 应用层(业务逻辑实现) ├── other-adapter # 适配层(REST API、Controller) ├── other-inner-endpoint # 内部服务端点(跨服务调用实现) └── other-start # 启动模块(Spring Boot入口) ``` --- ## 二、各模块详细说明 ### 1. other-domain(领域层) **作用**:定义核心领域模型和业务规则 **依赖**:无外部依赖(最纯净的业务模型) **路径**:`other-domain/src/main/java/com/red/circle/other/domain/` **职责**: - 定义领域实体(Entity) - 定义值对象(Value Object) - 定义领域服务接口 - 不包含任何基础设施相关代码 **典型文件**: - 领域实体定义 - 业务规则封装 --- ### 2. other-infrastructure(基础设施层) **作用**:提供技术基础设施支持 **依赖**:`other-domain`、框架组件(MyBatis、Redis、MongoDB、RocketMQ) **路径**:`other-infrastructure/src/main/java/com/red/circle/other/infra/` **目录结构**: ``` infra/ ├── database/ # 数据访问 │ ├── rds/ # MySQL 数据库 │ │ ├── dao/ # MyBatis DAO(继承BaseDAO) │ │ ├── entity/ # 数据库实体 │ │ ├── service/ # 数据库服务封装 │ │ └── query/ # 查询条件构建 │ ├── mongo/ # MongoDB 数据库 │ └── cache/ # Redis 缓存 ├── gateway/ # 网关实现(可选) ├── convertor/ # 对象转换器 ├── config/ # 配置类 └── utils/ # 工具类 ``` **关键说明**: - **DAO 命名规范**:`{实体名}DAO`,继承 `BaseDAO` - **Service 命名规范**:`{功能}DatabaseService` - **不是所有功能都需要 Gateway**,简单功能直接在 CmdExe 中调用 Infrastructure 的接口 --- ### 3. other-client(客户端接口层) **作用**:定义对外暴露的接口和数据模型 **依赖**:无 **路径**:`other-client/src/main/java/com/red/circle/other/app/` **目录结构**: ``` app/ ├── dto/ # 数据传输对象 │ ├── cmd/ # 命令对象(写操作) │ │ └── {功能模块}/ │ └── clientobject/ # 客户端对象(读操作返回值) │ └── {功能模块}/ ├── service/ # 服务接口定义 │ └── {功能模块}/ └── enums/ # 枚举类 ``` **命名规范**: - **Cmd(命令)**:用于写操作,如 `LotteryDrawCmd`、`UserProfileModifyCmd` - **CO(客户端对象)**:用于读操作返回,如 `LotteryActivityCO`、`UserProfileCO` - **QryCmd(查询命令)**:用于查询操作,如 `LotteryRecordQryCmd` **注解规范**: - 使用 `@Data` + 文件格式注释 - Cmd 需要时继承 `com.red.circle.common.business.dto.cmd.AppExtCommand` - 分页查询继承 `com.red.circle.common.business.dto.PageQueryCmd` - 校验注解使用 `jakarta.validation.constraints.*`(不用 javax) --- ### 4. other-application(应用层) **作用**:实现业务逻辑 **依赖**:`other-client`、`other-infrastructure` **路径**:`other-application/src/main/java/com/red/circle/other/app/` **目录结构**: ``` app/ ├── command/ # 命令执行器 │ └── {功能模块}/ │ ├── {功能}CmdExe.java # 写操作执行器 │ └── query/ # 查询执行器 │ └── {功能}QryExe.java ├── service/ # 服务实现 │ └── {功能模块}/ │ └── {功能}ServiceImpl.java ├── convertor/ # 对象转换器(MapStruct) ├── listener/ # 事件监听器 ├── scheduler/ # 定时任务 ├── manager/ # 业务管理器(复杂业务编排) ├── common/ # 通用业务逻辑 └── config/ # 应用配置 ``` **核心原则**: - **ServiceImpl 调用 CmdExe**:ServiceImpl 是门面,具体业务由 CmdExe 实现 - **CmdExe 命名规范**:`{功能名}CmdExe` 或 `{功能名}QryExe` - **是否需要 Gateway**: - ✅ 简单功能:CmdExe 直接调用 Infrastructure 的 DatabaseService - ✅ 复杂功能:CmdExe 通过 Gateway 封装复杂的数据访问逻辑 - 原则:**简单直接,复杂封装,不要过度设计** **代码质量要求**: - 关键业务必须添加 `@Transactional` 事务 - 金额操作必须保证幂等性(使用 bizNo) - 重要操作记录日志(`@Slf4j`) - 完善的参数校验和异常处理 --- ### 5. other-adapter(适配层) **作用**:对外提供 REST API **依赖**:`other-application` **路径**:`other-adapter/src/main/java/com/red/circle/other/adapter/app/` **目录结构**: ``` adapter/app/ └── {功能模块}/ └── {功能名}RestController.java ``` **命名规范**: - Controller 命名:`{功能名}RestController` - 继承 `BaseController` - 使用 `@RestController` + `@RequestMapping` **注解规范**: - API 文档使用 `@eo.` 系列注解(不使用 Swagger) - 路径规范:`/功能模块/资源`,如 `/activity/lottery` **职责**: - 接收 HTTP 请求 - 参数校验(`@Validated`) - 调用 Service 层 - 返回统一格式的响应 --- ### 6. other-inner-endpoint(内部服务端点) **作用**:提供跨服务调用的实现(供其他微服务通过 Feign 调用) **依赖**:`other-application`、`other-inner-api` **路径**:`other-inner-endpoint/src/main/java/com/red/circle/other/app/inner/` **与 other-client 的区别**: | 维度 | other-client | other-inner-endpoint | |------|-------------|---------------------| | 调用方 | 前端 App、Web | 其他微服务(如 wallet、live) | | 传输协议 | HTTP REST | HTTP Feign | | 接口定义位置 | `other-client/service/` | `other-inner-api/endpoint/` | | 接口实现位置 | `other-application/service/` | `other-inner-endpoint/service/` | | DTO 定义位置 | `other-client/dto/` | `other-inner-model/model/` | **典型场景**: - 钱包服务调用 other 服务获取用户信息 - 直播服务调用 other 服务更新房间状态 --- ### 7. other-start(启动模块) **作用**:Spring Boot 应用入口 **依赖**:`other-adapter`、`other-inner-endpoint` **路径**:`other-start/src/main/java/com/red/circle/` **职责**: - 定义 Spring Boot 主类 - 引入所有模块依赖 - 配置文件(application.yml) --- ## 三、跨模块通信说明 ### 内部模块调用关系图 ``` ┌─────────────────────────────────────────────────┐ │ other-start │ │ (Spring Boot 入口) │ └────────────┬────────────────────┬────────────────┘ │ │ ┌────────▼────────┐ ┌───────▼────────────┐ │ other-adapter │ │ other-inner-endpoint│ │ (REST API) │ │ (Feign 服务端) │ └────────┬────────┘ └───────┬────────────┘ │ │ └────────┬───────────┘ │ ┌─────────▼──────────┐ │ other-application │ │ (业务逻辑层) │ │ ├── CmdExe │ │ ├── ServiceImpl │ │ └── Manager │ └─────────┬──────────┘ │ ┌─────────────┼─────────────┐ │ │ │ ┌───────▼────────┐ ┌─▼──────────┐ ┌▼──────────┐ │ other-client │ │ other-infra│ │other-domain│ │ (接口+DTO) │ │ (数据访问) │ │ (实体) │ └────────────────┘ └────────────┘ └───────────┘ ``` ### 跨服务调用说明 **场景示例**:钱包服务需要调用 other 服务获取用户信息 1. **定义 API 接口**(在 `rc-inner-api/other-inner/other-inner-api/`): ```java // endpoint 包下定义 Feign 客户端接口 @FeignClient(name = "other-service") public interface UserProfileClientService { UserProfileDTO getByUserId(Long userId); } ``` 2. **定义数据模型**(在 `rc-inner-api/other-inner/other-inner-model/`): ```java // model 包下定义传输对象 public class UserProfileDTO { private Long userId; private String nickname; // ... } ``` 3. **实现服务端点**(在 `other-inner-endpoint/`): ```java @RestController public class UserProfileClientServiceImpl implements UserProfileClientService { @Autowired private UserProfileService userProfileService; public UserProfileDTO getByUserId(Long userId) { // 调用 application 层服务 return userProfileService.getByUserId(userId); } } ``` 4. **钱包服务调用**(在 wallet 服务中): ```java @Service public class WalletServiceImpl { @Autowired private UserProfileClientService userProfileClient; // Feign 客户端 public void someMethod() { UserProfileDTO user = userProfileClient.getByUserId(123L); } } ``` --- ## 四、新增功能开发流程 ### 标准开发顺序(必须遵循) 以"抽奖活动"为例,演示完整的开发流程: #### 1. 定义数据模型(other-client) ``` other-client/src/main/java/com/red/circle/other/app/dto/ ├── cmd/activity/ │ ├── LotteryDrawCmd.java # 抽奖命令 │ └── LotteryRecordQryCmd.java # 查询抽奖记录 └── clientobject/activity/ ├── LotteryActivityCO.java # 活动信息 └── LotteryDrawResultCO.java # 抽奖结果 ``` #### 2. 定义 Service 接口(other-client) ```java // other-client/src/main/java/com/red/circle/other/app/service/activity/ public interface LotteryActivityRestService { LotteryDrawResultCO draw(LotteryDrawCmd cmd); PageResult listMyRecords(LotteryRecordQryCmd cmd); } ``` #### 3. 实现 CmdExe(other-application) ``` other-application/src/main/java/com/red/circle/other/app/command/activity/ ├── LotteryDrawExe.java # 抽奖执行器 └── query/ └── LotteryRecordQryExe.java # 查询执行器 ``` **示例代码**: ```java @Component @RequiredArgsConstructor public class LotteryDrawExe { private final LotteryActivityDatabaseService lotteryDbService; private final LotteryPrizeGrantService prizeService; @Transactional public LotteryDrawResultCO execute(LotteryDrawCmd cmd) { // 1. 参数校验 validateTicket(cmd.getUserId()); // 2. 扣除抽奖券(直接调用 Infrastructure 的服务) lotteryDbService.consumeTicket(cmd.getUserId(), cmd.getActivityId()); // 3. 随机抽奖 LotteryPrize prize = prizeService.randomDraw(cmd.getActivityId()); // 4. 记录中奖 lotteryDbService.saveDrawRecord(cmd.getUserId(), prize); // 5. 返回结果 return convertToResult(prize); } } ``` #### 4. 实现 ServiceImpl(other-application) ```java @Service @RequiredArgsConstructor public class LotteryActivityRestServiceImpl implements LotteryActivityRestService { private final LotteryDrawExe lotteryDrawExe; private final LotteryRecordQryExe recordQryExe; @Override public LotteryDrawResultCO draw(LotteryDrawCmd cmd) { return lotteryDrawExe.execute(cmd); } @Override public PageResult listMyRecords(LotteryRecordQryCmd cmd) { return recordQryExe.query(cmd); } } ``` #### 5. 创建 Controller(other-adapter) ```java @RestController @RequestMapping("/activity/lottery") @RequiredArgsConstructor public class LotteryActivityRestController extends BaseController { private final LotteryActivityRestService lotteryService; /** * 执行抽奖 * @eo.name 执行抽奖 * @eo.url /draw * @eo.method post */ @PostMapping("/draw") public LotteryDrawResultCO draw(@RequestBody @Validated LotteryDrawCmd cmd) { return lotteryService.draw(cmd); } } ``` #### 6. 数据访问层(other-infrastructure) **简单功能**(直接使用 DAO 和 DatabaseService): ```java // DAO public interface LotteryTicketDAO extends BaseDAO {} // DatabaseService @Service public class LotteryActivityDatabaseService { @Autowired private LotteryTicketDAO ticketDAO; public void consumeTicket(Long userId, Long activityId) { // 直接操作数据库 } } ``` **复杂功能**(使用 Gateway 封装): ```java // Gateway 接口 public interface LotteryActivityGateway { void consumeTicketWithLock(Long userId, Long activityId); } // Gateway 实现 @Component public class LotteryActivityGatewayImpl implements LotteryActivityGateway { @Autowired private LotteryTicketDAO ticketDAO; @Autowired private RedisTemplate redisTemplate; @Override public void consumeTicketWithLock(Long userId, Long activityId) { // 分布式锁 + 数据库操作 + 缓存更新 String lockKey = "lottery:lock:" + userId; // ... 复杂逻辑 } } ``` #### 7. 跨服务调用(可选) 如果需要被其他服务调用,创建 Inner API: ``` rc-inner-api/other-inner/ ├── other-inner-api/endpoint/activity/ │ └── LotteryActivityClientService.java # Feign 接口 ├── other-inner-model/model/activity/ │ └── LotteryDrawDTO.java # 数据模型 └── other-inner-endpoint/service/activity/ └── LotteryActivityClientServiceImpl.java # 实现 ``` --- ## 五、文件路径快速参考 ### 按功能类型查找路径 | 文件类型 | 路径模板 | 示例 | |---------|---------|------| | **Controller** | `other-adapter/src/main/java/com/red/circle/other/adapter/app/{模块}/` | `activity/LotteryActivityRestController.java` | | **Service 接口** | `other-client/src/main/java/com/red/circle/other/app/service/{模块}/` | `activity/LotteryActivityRestService.java` | | **Cmd** | `other-client/src/main/java/com/red/circle/other/app/dto/cmd/{模块}/` | `activity/LotteryDrawCmd.java` | | **CO** | `other-client/src/main/java/com/red/circle/other/app/dto/clientobject/{模块}/` | `activity/LotteryDrawResultCO.java` | | **CmdExe** | `other-application/src/main/java/com/red/circle/other/app/command/{模块}/` | `activity/LotteryDrawExe.java` | | **QryExe** | `other-application/src/main/java/com/red/circle/other/app/command/{模块}/query/` | `activity/query/LotteryRecordQryExe.java` | | **ServiceImpl** | `other-application/src/main/java/com/red/circle/other/app/service/{模块}/` | `activity/LotteryActivityRestServiceImpl.java` | | **Gateway** | `other-infrastructure/src/main/java/com/red/circle/other/infra/gateway/{模块}/` | `activity/LotteryActivityGatewayImpl.java` | | **DAO** | `other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/dao/` | `LotteryTicketDAO.java` | | **Entity** | `other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/entity/` | `LotteryTicket.java` | | **DatabaseService** | `other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/` | `LotteryActivityDatabaseService.java` | | **Inner API** | `rc-inner-api/other-inner/other-inner-api/endpoint/{模块}/` | `activity/LotteryActivityClientService.java` | | **Inner Model** | `rc-inner-api/other-inner/other-inner-model/model/{模块}/` | `activity/LotteryDrawDTO.java` | | **Inner Endpoint** | `other-inner-endpoint/src/main/java/com/red/circle/other/app/inner/service/{模块}/` | `activity/LotteryActivityClientServiceImpl.java` | ### 按模块查找路径 #### 常见功能模块 - `activity` - 活动相关 - `user` - 用户相关 - `room` - 房间相关 - `game` - 游戏相关 - `family` - 家族相关 - `gift` - 礼物相关 - `task` - 任务相关 - `material` - 素材/道具相关 --- ## 六、核心开发规范 ### 1. 命名规范 | 类型 | 命名规则 | 示例 | |------|---------|------| | Controller | `{功能}RestController` | `LotteryActivityRestController` | | Service 接口 | `{功能}Service` | `LotteryActivityRestService` | | Service 实现 | `{功能}ServiceImpl` | `LotteryActivityRestServiceImpl` | | CmdExe | `{功能}CmdExe` / `{功能}Exe` | `LotteryDrawExe` | | QryExe | `{功能}QryExe` | `LotteryRecordQryExe` | | Cmd | `{功能}Cmd` | `LotteryDrawCmd` | | QryCmd | `{功能}QryCmd` | `LotteryRecordQryCmd` | | CO | `{功能}CO` | `LotteryDrawResultCO` | | DAO | `{实体名}DAO` | `LotteryTicketDAO` | | Entity | `{实体名}` | `LotteryTicket` | | Gateway | `{功能}Gateway` + Impl | `LotteryActivityGateway` | ### 2. 注解规范 #### DTO 层 ```java @Data // Lombok public class LotteryDrawCmd extends AppExtCommand { @NotNull private Long activityId; @Min(1) @Max(10) private Integer drawCount; } ``` #### 分页查询 ```java @Data public class LotteryRecordQryCmd extends PageQueryCmd { private Long activityId; } ``` #### Controller 层 ```java @RestController @RequestMapping("/activity/lottery") @RequiredArgsConstructor // Lombok 构造器注入 public class LotteryActivityRestController extends BaseController { /** * 执行抽奖 * @eo.name 执行抽奖 * @eo.url /draw * @eo.method post * @eo.request-type json */ @PostMapping("/draw") public LotteryDrawResultCO draw(@RequestBody @Validated LotteryDrawCmd cmd) { return lotteryService.draw(cmd); } } ``` #### Service 层 ```java @Service @RequiredArgsConstructor @Slf4j // 日志 public class LotteryActivityRestServiceImpl implements LotteryActivityRestService { private final LotteryDrawExe lotteryDrawExe; @Override @Transactional // 需要事务的方法 public LotteryDrawResultCO draw(LotteryDrawCmd cmd) { log.info("用户抽奖: userId={}, activityId={}", cmd.getReqUserId(), cmd.getActivityId()); return lotteryDrawExe.execute(cmd); } } ``` ### 3. 分页返回 统一使用 `com.red.circle.framework.dto.PageResult`: ```java public PageResult listMyRecords(LotteryRecordQryCmd cmd) { // 查询数据 List records = queryRecords(cmd); // 查询总数 long total = countRecords(cmd); return PageResult.of(records, total, cmd.getPageNum(), cmd.getPageSize()); } ``` ### 4. 异常处理 使用业务异常,不直接抛出 RuntimeException: ```java if (ticket == null) { throw new BusinessException("抽奖券不足"); } ``` ### 5. 事务控制 - **写操作**:必须添加 `@Transactional` - **读操作**:不需要事务 - **跨服务调用**:考虑分布式事务或最终一致性 --- ## 七、关键设计原则 ### 1. Gateway 使用原则 **何时使用 Gateway**: - ✅ 需要分布式锁的场景 - ✅ 需要缓存 + 数据库双写的场景 - ✅ 需要多个数据源聚合的场景 - ✅ 需要复杂的事务协调 - ✅ 业务逻辑复杂,需要封装的场景 **何时不用 Gateway**: - ❌ 简单的 CRUD 操作 - ❌ 单表查询 - ❌ 不需要缓存的简单操作 - ❌ 逻辑简单清晰的场景 **原则**:简单直接,复杂封装,避免过度设计 ### 2. 层次调用原则 ``` Controller → Service → CmdExe → Gateway/DatabaseService → DAO ``` - Controller 只调用 Service - Service 只调用 CmdExe - CmdExe 可直接调用 Infrastructure(简单场景)或通过 Gateway(复杂场景) - 禁止跨层调用 ### 3. 代码质量原则 参考主项目文档中的质量检查清单: - [ ] 命名清晰(避免 a、b、temp) - [ ] 完整的注释 - [ ] 参数校验(@Valid、@NotNull) - [ ] 异常处理 - [ ] 并发安全(金额操作) - [ ] 幂等性(bizNo) - [ ] 事务注解(@Transactional) - [ ] 关键日志(log.info/error) - [ ] 性能考虑(N+1、缓存) - [ ] 安全防护(SQL 注入、XSS) --- ## 八、常见问题 FAQ ### Q1: 什么时候需要 Gateway? A: 当业务逻辑涉及多个数据源、分布式锁、缓存同步等复杂场景时使用 Gateway。简单的 CRUD 直接使用 DatabaseService。 ### Q2: CmdExe 和 ServiceImpl 的区别? A: ServiceImpl 是门面,负责接口定义和调用编排。CmdExe 是具体的业务逻辑实现。一个 ServiceImpl 可能调用多个 CmdExe。 ### Q3: 跨服务调用如何实现? A: 通过 Inner API + Feign。在 `other-inner-api` 定义接口,在 `other-inner-endpoint` 实现,其他服务通过 Feign 客户端调用。 ### Q4: 如何处理分布式事务? A: 优先考虑最终一致性(如消息队列)。如果必须强一致,考虑 TCC 或 Saga 模式,但要评估复杂度。 ### Q5: MongoDB 和 MySQL 如何选择? A: - MySQL:强一致性、事务、关系查询 - MongoDB:高性能、灵活 schema、大数据量统计 --- ## 九、总结 ### 开发新功能的黄金路径 1. ✅ 先定义 DTO(Cmd、CO) 2. ✅ 定义 Service 接口 3. ✅ 实现 CmdExe(核心业务逻辑) 4. ✅ 实现 ServiceImpl(调用 CmdExe) 5. ✅ 创建 Controller(暴露 API) 6. ✅ 根据需要创建 Gateway 或直接用 DatabaseService 7. ✅ 如需跨服务调用,添加 Inner API ### 核心理念 - **领域驱动**:按业务领域划分模块 - **分层清晰**:各层职责明确,禁止跨层调用 - **简单优先**:不过度设计,够用即可 - **质量第一**:代码清晰、测试完善、文档齐全 --- **文档版本**:v1.0 **最后更新**:2025-01-15 **维护者**:开发团队