23 KiB
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<T> - 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 服务获取用户信息
- 定义 API 接口(在
rc-inner-api/other-inner/other-inner-api/):
// endpoint 包下定义 Feign 客户端接口
@FeignClient(name = "other-service")
public interface UserProfileClientService {
UserProfileDTO getByUserId(Long userId);
}
- 定义数据模型(在
rc-inner-api/other-inner/other-inner-model/):
// model 包下定义传输对象
public class UserProfileDTO {
private Long userId;
private String nickname;
// ...
}
- 实现服务端点(在
other-inner-endpoint/):
@RestController
public class UserProfileClientServiceImpl implements UserProfileClientService {
@Autowired
private UserProfileService userProfileService;
public UserProfileDTO getByUserId(Long userId) {
// 调用 application 层服务
return userProfileService.getByUserId(userId);
}
}
- 钱包服务调用(在 wallet 服务中):
@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)
// other-client/src/main/java/com/red/circle/other/app/service/activity/
public interface LotteryActivityRestService {
LotteryDrawResultCO draw(LotteryDrawCmd cmd);
PageResult<LotteryRecordCO> listMyRecords(LotteryRecordQryCmd cmd);
}
3. 实现 CmdExe(other-application)
other-application/src/main/java/com/red/circle/other/app/command/activity/
├── LotteryDrawExe.java # 抽奖执行器
└── query/
└── LotteryRecordQryExe.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)
@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<LotteryRecordCO> listMyRecords(LotteryRecordQryCmd cmd) {
return recordQryExe.query(cmd);
}
}
5. 创建 Controller(other-adapter)
@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):
// DAO
public interface LotteryTicketDAO extends BaseDAO<LotteryTicket> {}
// DatabaseService
@Service
public class LotteryActivityDatabaseService {
@Autowired
private LotteryTicketDAO ticketDAO;
public void consumeTicket(Long userId, Long activityId) {
// 直接操作数据库
}
}
复杂功能(使用 Gateway 封装):
// 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 层
@Data // Lombok
public class LotteryDrawCmd extends AppExtCommand {
@NotNull
private Long activityId;
@Min(1)
@Max(10)
private Integer drawCount;
}
分页查询
@Data
public class LotteryRecordQryCmd extends PageQueryCmd {
private Long activityId;
}
Controller 层
@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 层
@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<T>:
public PageResult<LotteryRecordCO> listMyRecords(LotteryRecordQryCmd cmd) {
// 查询数据
List<LotteryRecordCO> records = queryRecords(cmd);
// 查询总数
long total = countRecords(cmd);
return PageResult.of(records, total, cmd.getPageNum(), cmd.getPageSize());
}
4. 异常处理
使用业务异常,不直接抛出 RuntimeException:
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、大数据量统计
九、总结
开发新功能的黄金路径
- ✅ 先定义 DTO(Cmd、CO)
- ✅ 定义 Service 接口
- ✅ 实现 CmdExe(核心业务逻辑)
- ✅ 实现 ServiceImpl(调用 CmdExe)
- ✅ 创建 Controller(暴露 API)
- ✅ 根据需要创建 Gateway 或直接用 DatabaseService
- ✅ 如需跨服务调用,添加 Inner API
核心理念
- 领域驱动:按业务领域划分模块
- 分层清晰:各层职责明确,禁止跨层调用
- 简单优先:不过度设计,够用即可
- 质量第一:代码清晰、测试完善、文档齐全
文档版本:v1.0
最后更新:2025-01-15
维护者:开发团队