# DataManager 重要决策记录

**版本：** 3.0  
**最后更新**: 2026-01-17

---

本文档记录了 DataManager 模块的重要架构设计决策，包括问题背景、可选方案、决策理由和影响。

---

## 决策 1：三层架构设计

**日期**：2025-12-19  
**状态**：已实施

### 痛点

在系统演进过程中，数据访问逻辑逐渐分散，出现了以下问题：
- 数据访问方法散落在多个模块，缺乏统一入口
- 业务逻辑与数据访问耦合，难以维护和测试
- 底层 Model 直接暴露，导致外部代码依赖实现细节
- 多次数据库查询效率低下，缺乏优化
- 职责不清：服务之间职责边界模糊
- 实现细节泄露：外部代码依赖 Model 的内部实现

### 可选方案

**方案 A：单层架构（所有逻辑在一个类中）**
- 优点：简单直接
- 缺点：职责不清，难以维护，代码臃肿

**方案 B：两层架构（Facade + Service）**
- 优点：职责分离，易于理解
- 缺点：跨服务协调逻辑无处放置

**方案 C：三层架构（Facade + Coordinator + Service）** ✅
- 优点：职责清晰，易于扩展，支持跨服务协调
- 缺点：层级稍多，学习成本略高

### 为什么选择当前方案

选择方案 C（三层架构）的理由：
1. **职责清晰**：Facade 负责生命周期管理，Coordinator 负责跨服务协调，Service 负责领域逻辑
2. **易于扩展**：添加新服务只需在对应层级添加，不影响其他层级
3. **支持复杂场景**：Coordinator 层可以处理需要跨多个服务的复杂请求
4. **符合设计原则**：遵循单一职责原则和开闭原则

### 架构设计细节

**1. 严格职责分离，不提供便捷委托方法**
- **原因**：避免职责不清、方法冗余、维护困难
- **实现**：每个服务只负责自己的领域，不提供跨服务的便捷委托方法
- **影响**：访问路径稍长（如 `data_mgr.stock.kline.load()`），但意图更明确

**2. 底层 Model 完全私有化**
- **原因**：避免实现细节泄露、难以重构、行为不一致
- **实现**：Model 类完全私有化，只能通过 DataService 访问
- **影响**：外部代码无法直接访问 Model，必须通过 DataService，但可以自由重构 Model 层

### 影响

- 代码结构清晰，易于理解和维护
- 新服务添加简单，只需在对应层级实现
- 跨服务协调逻辑集中在 Coordinator 层，便于管理
- 外部代码不依赖底层实现，只依赖稳定的 DataService API
- 可以自由重构 Model 层，而不影响外部代码

---

## 决策 3：初始化和单例设计

**日期**：2025-12-19  
**状态**：已实施

### 痛点

在早期设计中，存在以下问题：
- **使用不便**：需要手动调用 `initialize()` 方法，容易忘记初始化
- **错误频发**：未初始化就使用会导致错误
- **多进程问题**：如果使用全局单例，多进程环境下进程间内存不共享，资源管理复杂

### 可选方案

**方案 A：手动初始化 + 全局单例**
- 优点：控制灵活，只有一个实例
- 缺点：使用不便，容易出错，多进程环境下不可行

**方案 B：自动初始化 + 进程级单例** ✅
- 优点：使用方便，不易出错，多进程友好
- 缺点：每个进程有独立实例（这是期望的行为）

### 为什么选择当前方案

选择方案 B 的理由：

**1. 自动初始化设计**
- **使用方便**：创建实例后即可使用，无需手动初始化
- **减少错误**：避免忘记初始化导致的错误
- **幂等性保证**：`initialize()` 方法是幂等的，多次调用只执行一次
- **灵活性保留**：保留显式 `initialize()` 方法，支持特殊场景

**2. 进程级单例模式**
- **多进程友好**：每个进程有独立的实例，避免进程间共享问题
- **资源管理简单**：每个进程独立管理自己的数据库连接池
- **符合 Python 多进程模型**：进程间内存不共享，单例也应该是进程级的
- **线程安全**：使用线程锁保证单例创建的线程安全

### 影响

- 使用更方便，创建后即可使用
- 减少初始化相关的错误
- 特殊场景仍可通过显式调用控制初始化时机
- 多进程环境下每个进程有独立的 DataManager 实例
- 资源（如连接池）在每个进程独立管理
- 线程安全，支持多线程环境

---

## 决策 2：Schema 和 Model 设计

**日期**：2025-12-19  
**状态**：已实施

### 痛点

在早期设计中，表结构定义和 Model 设计存在以下问题：
- **表结构定义不清晰**：需要在代码中查找 CREATE TABLE 语句，难以维护
- **多数据库支持困难**：不同数据库的 SQL 语法不同
- **缺乏类型安全**：使用通用 Model 无法区分不同表的操作
- **业务方法无处放置**：常用的业务查询方法无法封装
- **代码重复**：每个 Model 都需要实现相同的 CRUD 逻辑

### 可选方案

**方案 A：代码中定义表结构 + 通用 Model**
- 优点：实现简单
- 缺点：不清晰，难以维护，缺乏类型安全，代码重复

**方案 B：使用 ORM（如 SQLAlchemy）**
- 优点：功能强大，支持迁移
- 缺点：学习成本高，性能开销，过度设计

**方案 C：Schema 驱动 + 每个表独立 Model + 继承基类** ✅
- 优点：清晰、易维护、类型安全、代码复用
- 缺点：需要实现 Schema 到 SQL 的转换逻辑

### 为什么选择当前方案

选择方案 C 的理由：

**1. Schema 驱动的表初始化**
- **声明式设计**：Schema 是表结构的声明，不包含实现细节
- **易于维护**：修改表结构只需修改 JSON 文件
- **多数据库支持**：SchemaManager 可以根据数据库类型生成对应的 SQL
- **文档化**：Schema 文件本身就是文档，清晰易懂
- **自动化**：系统启动时自动读取 Schema 并创建表

**2. 每个 Schema 对应一个 Model**
- **职责单一**：每个 Model 只负责一个表的操作，职责清晰
- **业务方法封装**：可以在 Model 中封装常用的业务查询方法
- **类型安全**：每个 Model 对应一个表，类型明确，IDE 支持更好
- **代码复用**：业务查询方法可以在 Model 中复用，避免重复代码

**3. Model 继承 DbBaseModel**
- **代码复用**：通用的 CRUD 操作在基类中实现，所有 Model 复用
- **维护简单**：修改基础功能只需修改基类，所有 Model 自动受益
- **行为一致**：所有 Model 的行为一致，减少 bug
- **功能丰富**：基类提供了丰富的功能（批量操作、分页查询、时序数据优化等）

### 影响

- 表结构定义清晰，易于理解和维护
- 支持多数据库（PostgreSQL、MySQL、SQLite）
- 系统启动时自动创建表，无需手动执行 SQL
- 需要为每个表创建 Model 类（但代码量不大）
- 业务查询方法可以封装在 Model 中，提高代码复用性
- 类型更安全，IDE 支持更好
- Model 实现更简单，只需关注业务特定的查询方法

---

## 决策 4：引入 Service 层

**日期**：2025-12-19  
**状态**：已实施

### 痛点

如果只有 Model 层，会出现以下问题：
- **跨表查询困难**：Model 只负责单表操作，跨表查询需要在业务代码中实现
- **业务逻辑分散**：业务逻辑散落在各个使用 Model 的地方
- **数据转换重复**：每个使用的地方都需要做相同的数据转换
- **实现细节暴露**：业务代码需要知道底层有哪些表，如何组合

### 可选方案

**方案 A：只有 Model 层，业务代码直接使用 Model**
- 优点：简单直接
- 缺点：跨表查询困难，业务逻辑分散，代码重复

**方案 B：引入 Service 层，封装业务逻辑** ✅
- 优点：业务逻辑集中，跨表查询简单，数据转换统一
- 缺点：增加一层抽象（但这是值得的）

### 为什么选择当前方案

选择方案 B 的理由：
1. **业务逻辑集中**：Service 封装领域特定的业务逻辑，业务代码更简洁
2. **跨表查询简单**：Service 可以组合多个 Model 完成复杂查询
3. **数据转换统一**：Service 统一处理数据格式转换，避免重复代码
4. **封装实现细节**：Service 隐藏底层表结构，外部代码不依赖实现细节

### 影响

- 增加了一层抽象，但业务代码更简洁
- 业务逻辑集中在 Service 层，易于维护
- 可以自由重构底层表结构，不影响外部代码

---

## 决策 5：淘汰 prepare_data 方法

**日期**：2026-01-17  
**状态**：已淘汰

### 痛点

早期设计中，`prepare_data` 方法用于解析 Tag 或 Strategy 的 settings 配置，根据配置自动加载所需数据。但在实际使用中发现：

1. **数据加载逻辑不同**：Tag 和 Strategy 模块的数据加载需求差异很大
   - **Tag 模块**：需要支持 chunk 模式（滑动窗口）、增量更新、lookback 数据等复杂场景
   - **Strategy 模块**：需要全量加载、游标机制逐日过滤、支持多周期数据等不同场景
   
2. **无法统一抽象**：两种场景的数据加载逻辑差异太大，无法用一个通用的 `prepare_data` 方法满足

3. **实际未使用**：Tag 和 Strategy 模块都实现了各自的数据管理器（`TagWorkerDataManager`、`StrategyWorkerDataManager`），不依赖 `prepare_data`

### 可选方案

**方案 A：保留 prepare_data，继续完善**
- 优点：统一的 API
- 缺点：无法满足不同场景的复杂需求，代码会变得臃肿

**方案 B：为每个模块实现独立的数据管理器** ✅
- 优点：可以针对不同场景优化，代码清晰
- 缺点：代码重复（但可以通过基类减少重复）

### 为什么选择当前方案

选择方案 B 的理由：
1. **场景差异大**：Tag 和 Strategy 的数据加载需求差异太大，无法统一抽象
2. **性能优化**：独立的数据管理器可以针对各自场景进行性能优化（如 Tag 的 chunk 模式、Strategy 的游标机制）
3. **职责清晰**：每个模块的数据管理器专注于自己的场景，职责清晰
4. **易于维护**：独立实现，互不影响，易于维护和扩展

### 影响

- `prepare_data` 方法被标记为淘汰，不再推荐使用
- Tag 模块使用 `TagWorkerDataManager` 处理数据加载
- Strategy 模块使用 `StrategyWorkerDataManager` 处理数据加载
- 两个数据管理器都继承或使用 DataManager 的底层能力（Model 层），但数据加载逻辑独立实现

---

## 相关文档

- **[overview.md](./overview.md)**：模块概览
- **[architecture.md](./architecture.md)**：架构文档

> **提示**：本文档记录了 DataManager 的重要设计决策。如需了解完整的架构设计，请参考 [architecture.md](./architecture.md)。

---

**文档结束**
