Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
230 changes: 230 additions & 0 deletions .trae/documents/cqrs_injection_fix_plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
# CQRS 代理注入问题修复方案

## 问题分析

### 错误描述

Spring Boot 启动时抛出 `NoUniqueBeanDefinitionException`:

```
Field delegate in cn.structure.infra.repository.RepositoryFacade required a single bean, but 3 were found:
- userReadDelegate
- userWriteDelegate
- userMybatisPlusDelegate
```

### 根本原因

`RepositoryFacade` 的 `delegate` 字段使用 `@Autowired(required = false)` 注入,但当存在多个匹配类型的 Bean 时,Spring 无法确定注入哪一个。`required = false` 仅在没有匹配 Bean 时生效,无法解决多 Bean 歧义问题。

### 当前状态

| 组件 | 注解 | 问题 |
| ----------------------------------- | ------------------------------ | ----------- |
| `UserReadDelegate` | `@Component` + `@ReadDelegate` | 已标记读代理 |
| `UserWriteDelegate` | `@Component` | 未标记写代理 |
| `RepositoryFacade.delegate` | `@Autowired(required = false)` | 无限定符,无法区分读写 |
| `CqrsRepositoryFacade.readDelegate` | `@Autowired(required = false)` | 无限定符,无法精准注入 |

***

## 解决方案

### 设计思路

通过**注解标记 + 自定义限定符**机制,让 Spring 能够根据代理类型(读/写)精准注入:

1. **新增** **`@WriteDelegate`** **注解**:标记写代理/基础代理
2. **修改** **`RepositoryFacade`**:`delegate` 字段注入写代理(排除 `@ReadDelegate`)
3. **修改** **`CqrsRepositoryFacade`**:`readDelegate` 字段注入读代理(仅 `@ReadDelegate`)
4. **保留** **`@ReadDelegate`** **注解**:保持现有功能不变

### 架构设计

```
┌─────────────────────────────────────────────────────────────────┐
│ Spring Context │
│ ┌──────────────────┐ ┌──────────────────┐ ┌───────────────┐ │
│ │ @Component │ │ @Component │ │ @Component │ │
│ │ @ReadDelegate │ │ @WriteDelegate │ │ (无标记) │ │
│ │ UserReadDelegate │ │ UserWriteDelegate│ │ Others... │ │
│ └────────┬─────────┘ └────────┬─────────┘ └───────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌───────────────────────────────────────────────┐ │
│ │ RepositoryBeanPostProcessor │ │
│ │ - 根据注解筛选 Bean,注入到 Facade 对应字段 │ │
│ └───────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
```

***

## 修改步骤

### 步骤 1:新增 `@WriteDelegate` 注解

**文件位置**:`structure-infra-starter/src/main/java/cn/structure/infra/annotations/WriteDelegate.java`

**内容**:

```java
@Qualifier
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WriteDelegate {
}
```

***

### 步骤 2:修改 `RepositoryFacade` 注入逻辑

**文件位置**:`structure-infra-starter/src/main/java/cn/structure/infra/repository/RepositoryFacade.java`

**修改内容**:

* 将 `@Autowired(required = false)` 替换为自定义注入逻辑

* 通过 `@Qualifier` 或 BeanPostProcessor 确保只注入写代理(无 `@ReadDelegate` 标记的 Bean)

***

### 步骤 3:修改 `CqrsRepositoryFacade` 注入逻辑

**文件位置**:`structure-infra-starter/src/main/java/cn/structure/infra/repository/CqrsRepositoryFacade.java`

**修改内容**:

* 将 `readDelegate` 字段添加 `@ReadDelegate` 限定符

* 确保 `delegate` 字段(继承自父类)注入写代理

***

### 步骤 4:创建 `RepositoryBeanPostProcessor`

**文件位置**:`structure-infra-starter/src/main/java/cn/structure/infra/repository/RepositoryBeanPostProcessor.java`

**功能**:

* 遍历所有 `RepositoryFacade` 和 `CqrsRepositoryFacade` 实例

* 根据泛型类型和注解标记,精准注入对应的 delegate

* 优先注入标记了 `@WriteDelegate` 的 Bean 作为 baseDelegate

* 优先注入标记了 `@ReadDelegate` 的 Bean 作为 readDelegate

***

### 步骤 5:更新 `AutoRepositoryConfiguration`

**文件位置**:`structure-infra-starter/src/main/java/cn/structure/infra/configuration/AutoRepositoryConfiguration.java`

**修改内容**:

* 注册 `RepositoryBeanPostProcessor`

***

### 步骤 6:更新 `UserWriteDelegate` 示例

**文件位置**:`structure-infra-sample/structure-infra-sample-cqrs/src/main/java/cn/structure/infra/sample/cqrs/infra/delegate/write/UserWriteDelegate.java`

**修改内容**:

* 添加 `@WriteDelegate` 注解

***

## 关键代码设计

### 1. RepositoryBeanPostProcessor 核心逻辑

```java
public class RepositoryBeanPostProcessor implements BeanPostProcessor {

@Autowired
private ListableBeanFactory beanFactory;

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean instanceof CqrsRepositoryFacade) {
processCqrsFacade((CqrsRepositoryFacade<?, ?, ?, ?>) bean);
} else if (bean instanceof RepositoryFacade) {
processRepositoryFacade((RepositoryFacade<?, ?, ?>) bean);
}
return bean;
}

private void processCqrsFacade(CqrsRepositoryFacade<?, ?, ?, ?> facade) {
// 通过泛型获取代理类型
Type[] types = GenericTypeResolver.resolveTypeArguments(
facade.getClass(), CqrsRepositoryFacade.class);

// 注入写代理(优先 @WriteDelegate,其次无 @ReadDelegate 的 Bean)
Object writeDelegate = findDelegate((Class<?>) types[2], true);
// 注入读代理(优先 @ReadDelegate 标记的 Bean)
Object readDelegate = findDelegate((Class<?>) types[3], false);

// 通过反射设置字段
}
}
```

### 2. delegate 查找策略

| 场景 | 优先级 | 说明 |
| ------ | ----------------------- | -------- |
| 写代理注入 | 1. `@WriteDelegate` 标记 | 显式标记的写代理 |
| <br /> | 2. 无 `@ReadDelegate` 标记 | 默认作为写代理 |
| <br /> | 3. 任意匹配类型 | 兜底策略 |
| 读代理注入 | 1. `@ReadDelegate` 标记 | 显式标记的读代理 |
| <br /> | 2. 任意匹配类型 | 兜底策略 |

***

## 风险与注意事项

### 风险点

1. **泛型类型解析失败**:如果子类未正确声明泛型参数,可能导致注入失败
2. **多 Bean 冲突**:如果同一类型同时存在多个 `@WriteDelegate` 或 `@ReadDelegate`,仍会冲突
3. **性能影响**:BeanPostProcessor 在每个 Bean 创建时都会执行

### 注意事项

1. 确保所有 Delegate 实现类都标记了正确的注解
2. 对于非 CQRS 场景(单代理),不需要标记 `@WriteDelegate`,框架会自动选择无 `@ReadDelegate` 标记的 Bean
3. 保留 `required = false` 的容错机制,确保非 CQRS 场景也能正常运行

***

## 测试验证

### 验证步骤

1. **编译测试**:`mvn clean compile`
2. **单元测试**:`mvn test -pl structure-infra-sample/structure-infra-sample-cqrs`
3. **集成测试**:启动 CQRS 示例应用,验证代理正确注入

### 预期结果

* `UserCqrsRepository` 的 `delegate` 字段注入 `UserWriteDelegate`

* `UserCqrsRepository` 的 `readDelegate` 字段注入 `UserReadDelegate`

* 单代理模式的 `UserRepositoryImpl` 正常工作,不受影响

***

## 兼容性说明

| 场景 | 兼容 | 说明 |
| ------------ | -- | ------------------------------------------ |
| 单代理模式(现有代码) | 是 | 无需修改,框架自动选择无 `@ReadDelegate` 的 Bean |
| CQRS 模式(修改后) | 是 | 需要添加 `@WriteDelegate` 和 `@ReadDelegate` 注解 |
| 混合模式(多个模块) | 是 | 通过注解标记区分不同用途的代理 |

90 changes: 90 additions & 0 deletions .trae/documents/facade_refactor_plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# 仓储门面重构计划

## 问题分析

### 当前架构问题
1. **编译错误**:`RepositoryBeanPostProcessor.java` 引用了不存在的 `ICommandDelegate` 接口
2. **层次结构混乱**:当前 `RepositoryFacade extends CqrsRepositoryFacade`,与用户期望相反
3. **多余门面类**:`BaseRepositoryFacade` 和 `SimpleRepositoryFacade` 不需要,应移除
4. **CQRS 泛型不足**:`CqrsRepositoryFacade` 只定义了一个代理泛型,需要两个

### 用户期望的架构
```
RepositoryFacade<T, ID> // 基础门面(单代理模式)
└── CqrsRepositoryFacade<T, ID, D, RD> // CQRS门面(读写分离,两个代理)
```

## 修改方案

### 1. 修复 RepositoryBeanPostProcessor 编译错误
- **文件**:`structure-infra-starter/src/main/java/cn/structure/infra/repository/RepositoryBeanPostProcessor.java`
- **修改内容**:移除 `ICommandDelegate` 引用,只保留 `RepositoryDelegate` 和 `IQueryDelegate` 检查

### 2. 重构 RepositoryFacade(单代理模式)
- **文件**:`structure-infra-starter/src/main/java/cn/structure/infra/repository/RepositoryFacade.java`
- **修改内容**:
- 移除泛型参数 `D`
- 添加 `protected RepositoryDelegate<T, ID> delegate`
- 实现完整的 CRUD 方法(从 BaseRepositoryFacade 迁移)

### 3. 重构 CqrsRepositoryFacade(双代理模式)
- **文件**:`structure-infra-starter/src/main/java/cn/structure/infra/repository/CqrsRepositoryFacade.java`
- **修改内容**:
- 继承 `RepositoryFacade<T, ID>`
- 添加两个泛型参数:`D extends RepositoryDelegate<T, ID>`(基础代理)和 `RD extends IQueryDelegate<T, ID>`(读代理)
- 添加 `protected D baseDelegate` 和 `protected RD readDelegate`
- 重写读方法,实现回退机制

### 4. 删除多余门面类
- **删除文件**:
- `structure-infra-starter/src/main/java/cn/structure/infra/repository/BaseRepositoryFacade.java`
- `structure-infra-starter/src/main/java/cn/structure/infra/repository/SimpleRepositoryFacade.java`

### 5. 更新示例代码

#### 5.1 更新 AbstractUserRepositoryImpl(单代理)
- **文件**:`structure-infra-sample/structure-infra-sample-core/src/main/java/cn/structure/infra/sample/infra/repository/AbstractUserRepositoryImpl.java`
- **修改内容**:改为继承 `RepositoryFacade<UserEntity, Long>`

#### 5.2 更新 UserCqrsRepository(双代理)
- **文件**:`structure-infra-sample/structure-infra-sample-cqrs/src/main/java/cn/structure/infra/sample/cqrs/infra/repositoory/UserCqrsRepository.java`
- **修改内容**:改为继承 `CqrsRepositoryFacade<UserEntity, Long, UserWriteDelegate, UserReadDelegate>`

### 6. 更新 RepositoryBeanPostProcessor
- **文件**:`structure-infra-starter/src/main/java/cn/structure/infra/repository/RepositoryBeanPostProcessor.java`
- **修改内容**:
- 支持单代理模式的 `RepositoryFacade`(注入 `delegate`)
- 支持双代理模式的 `CqrsRepositoryFacade`(注入 `baseDelegate` 和 `readDelegate`)

### 7. 修复测试代码
- **文件**:`structure-infra-sample/structure-infra-sample-cqrs/src/test/java/cn/structure/infra/sample/cqrs/UserCqrsRepositoryTest.java`
- **修改内容**:调整泛型类型和方法调用

## 依赖关系

```
RepositoryFacade (单代理)
├── AbstractUserRepositoryImpl
│ ├── UserRepositoryImpl (MyBatis)
│ ├── UserJpaRepositoryImpl
│ ├── UserMongoRepositoryImpl
│ └── UserElasticsearchRepositoryImpl
└── CqrsRepositoryFacade (双代理)
└── UserCqrsRepository
```

## 风险提示

1. **泛型兼容性**:修改泛型参数可能导致编译错误,需要全面检查所有继承 RepositoryFacade 的类
2. **测试影响**:测试代码中使用的 `getBaseDelegate()` 和 `getReadDelegate()` 方法名可能需要调整
3. **Bean 注入**:RepositoryBeanPostProcessor 需要正确识别单代理和双代理模式的门面

## 验证步骤

1. 编译整个项目,检查是否有编译错误
2. 运行 MyBatis、JPA、MongoDB 模块测试(单代理模式)
3. 运行 CQRS 模块测试(双代理模式)
4. 验证读操作回退机制是否正常工作
Loading
Loading