# 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 服务获取用户信息

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<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         # 查询执行器
```

**示例代码**：
```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<LotteryRecordCO> 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<LotteryTicket> {}

// 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<T>`：

```java
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：

```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  
**维护者**：开发团队