# DataSource + Handler 架构设计 V2

## 核心原则

### **职责分离**

```text
Strategy (业务逻辑层)
    ↓ 设置 action, 完成 ID 映射
DataSource (状态管理 + 编排层)
    ↓ 调用 Handler, 管理状态流转
Handler (纯执行层)
    ↓ 调用后端 API, 返回结果
Backend API
```

### **关键设计决策**

1. **ID 映射在 Strategy 完成** ✅
   - `Strategy.create()` / `Strategy.update()` 时，已将依赖字段的本地 ID 替换为远程 ID
   - DataSource 拿到的 `node.data` 已经是映射好的数据
   - Handler 直接使用 `node.data`，不需要关心 ID 映射

2. **状态管理在 DataSource** ✅
   - 所有 `node.status` 的转换由 DataSource 负责
   - Handler 只返回 `TaskResult`（成功/失败/进行中）
   - DataSource 根据 `TaskResult` 更新 `node.status`

3. **Handler 只负责执行** ✅
   - 调用后端 API（load/create/update/delete）
   - 返回执行结果（TaskResult）
   - 提供异步任务轮询（poll_tasks）
   - 提供数据质量检查（validate）

---

## Handler 接口设计

### **核心方法**

```python
class NodeHandler(Protocol[T]):
    # ===== 数据加载 =====
    async def load(self, collection: Optional[DataCollection] = None) -> List[Dict[str, Any]]:
        """
        从后端加载全部数据
        
        Args:
            collection: Collection 上下文（可选，用于依赖查询）
            
        Returns:
            原始数据列表
            
        示例：
            # Contract Handler（依赖 Project）
            async def load(self, collection=None):
                # 从 collection 获取 project 列表
                if collection:
                    projects = collection.filter(node_type="project")
                    project_ids = [p.data_id for p in projects if p.data_id]
                else:
                    project_ids = []
                
                # 只加载这些项目的合同
                response = await self.api.get_contracts(project_ids=project_ids)
                return response["data"]
        """
    
    # ===== 数据操作 =====
    async def create(self, node: SyncNode[T]) -> TaskResult:
        """
        创建节点
        
        注意：node.data 已经过 ID 映射，直接使用即可
        
        Returns:
            TaskResult:
                - SUCCESS: 同步完成，返回 data_id
                - IN_PROGRESS: 异步任务，返回 task_id
                - FAILED: 失败，返回 error
                
        示例：
            # 同步接口
            async def create(self, node):
                try:
                    response = await self.api.create_contract(node.data)
                    return TaskResult.success(data_id=response["id"])
                except Exception as e:
                    return TaskResult.failed(error=str(e))
            
            # 异步接口
            async def create(self, node):
                try:
                    response = await self.api.submit_contract_async(node.data)
                    return TaskResult.in_progress(task_id=response["task_id"])
                except Exception as e:
                    return TaskResult.failed(error=str(e))
        """
    
    async def update(self, node: SyncNode[T]) -> TaskResult:
        """更新节点（同 create）"""
    
    async def delete(self, node: SyncNode[T]) -> TaskResult:
        """删除节点（同 create）"""
    
    # ===== 异步任务轮询 =====
    async def poll_tasks(self, task_ids: List[str]) -> Dict[str, TaskResult]:
        """
        批量轮询异步任务状态
        
        Args:
            task_ids: 任务 ID 列表
            
        Returns:
            {task_id: TaskResult} 字典
            
        示例：
            async def poll_tasks(self, task_ids):
                response = await self.api.batch_query_tasks(task_ids)
                results = {}
                for task in response["tasks"]:
                    if task["status"] == "completed":
                        results[task["id"]] = TaskResult.success(
                            data_id=task["result"]["contract_id"]
                        )
                    elif task["status"] == "failed":
                        results[task["id"]] = TaskResult.failed(
                            error=task["error"]
                        )
                    else:  # running
                        results[task["id"]] = TaskResult.in_progress(
                            task_id=task["id"]
                        )
                return results
        """
    
    # ===== 数据质量检查 =====
    async def validate(self, data: Dict[str, Any]) -> ValidationResult:
        """
        数据质量检查
        
        Args:
            data: 原始数据
            
        Returns:
            ValidationResult（是否有效 + 错误列表）
            
        示例：
            async def validate(self, data):
                errors = []
                if not data.get("code"):
                    errors.append("合同编码为空")
                if not data.get("project_id"):
                    errors.append("项目 ID 为空")
                
                return ValidationResult(
                    is_valid=len(errors) == 0,
                    errors=errors
                )
        """
    
    # ===== 辅助方法 =====
    def extract_id(self, data: Dict[str, Any]) -> str:
        """从原始数据提取 ID"""
    
    def create_node(self, data: Dict[str, Any]) -> SyncNode[T]:
        """从原始数据创建 SyncNode"""
```

### **TaskResult 设计**

```python
class TaskStatus(Enum):
    SUCCESS = "success"          # 同步完成
    FAILED = "failed"            # 失败
    IN_PROGRESS = "in_progress"  # 异步任务进行中


class TaskResult:
    def __init__(
        self,
        status: TaskStatus,
        data_id: Optional[str] = None,    # 成功时返回的 ID
        task_id: Optional[str] = None,    # 异步任务 ID
        error: Optional[str] = None,      # 失败原因
        metadata: Optional[Dict] = None   # 其他元数据
    ):
        ...
    
    @classmethod
    def success(cls, data_id: Optional[str] = None) -> TaskResult:
        """创建成功结果"""
    
    @classmethod
    def failed(cls, error: str) -> TaskResult:
        """创建失败结果"""
    
    @classmethod
    def in_progress(cls, task_id: str) -> TaskResult:
        """创建进行中结果"""
```

---

## DataSource 接口设计

### **接口方法**

```python
class BaseDataSource(ABC):
    # ===== Handler 管理 =====
    def register_handler(self, handler: NodeHandler) -> None:
        """注册 Handler"""
    
    def get_handler(self, node_type: str) -> NodeHandler:
        """获取 Handler"""
    
    # ===== 数据加载 =====
    async def load_all(
        self,
        order: List[str]
    ) -> None:
        """
        按依赖顺序加载所有数据
        
        注意：调用前需先 set_collection()
        
        Args:
            order: 节点类型顺序（如 ["project", "contract", "construction"]）
            
        工作流程：
        1. 按顺序遍历每个节点类型
        2. 调用 Handler.load()
        3. Handler 可以从 self._collection 查询依赖节点
        4. 使用 Handler.create_node() 创建 SyncNode
        5. 添加到 Collection
        """
    
    # ===== 同步执行 =====
    async def sync_all(
        self,
        order: List[str]
    ) -> None:
        """
        按依赖顺序执行同步
        
        流程：
        1. 按顺序遍历每个节点类型
        2. 筛选需要同步的节点（status=PENDING, action!=NONE）
        3. 调用 Handler 的 create/update/delete
        4. 根据 TaskResult 更新节点状态
        5. 处理异步任务轮询
        """
    
    # ===== 统计信息 =====
    def get_sync_summary(self, collection: DataCollection) -> Dict[str, Any]:
        """获取同步统计信息"""
```

### **状态流转逻辑**

```python
async def _sync_nodes(self, handler, nodes):
    async_tasks = {}  # {node_id: task_id}
    
    for node in nodes:
        # 1. 跳过不需要处理的节点
        if node.action == SyncAction.NONE:
            continue
        
        # 2. 跳过异常状态节点
        if node.binding_status in [DEPENDENCY_ERROR, ABNORMAL, WARNING]:
            node.status = SyncStatus.SKIPPED
            continue
        
        # 3. 只处理 PENDING 节点
        if node.status != SyncStatus.PENDING:
            continue
        
        # 4. 执行操作
        try:
            node.status = SyncStatus.IN_PROGRESS
            
            if node.action == SyncAction.CREATE:
                result = await handler.create(node)
            elif node.action == SyncAction.UPDATE:
                result = await handler.update(node)
            elif node.action == SyncAction.DELETE:
                result = await handler.delete(node)
            
            # 5. 处理结果
            if result.status == TaskStatus.SUCCESS:
                node.status = SyncStatus.SUCCESS
                if result.data_id:
                    node.data_id = result.data_id  # 回填 ID
                    
            elif result.status == TaskStatus.FAILED:
                node.status = SyncStatus.FAILED
                node.error = result.error
                
            elif result.status == TaskStatus.IN_PROGRESS:
                async_tasks[node.node_id] = result.task_id
                
        except Exception as e:
            node.status = SyncStatus.FAILED
            node.error = str(e)
    
    # 6. 轮询异步任务
    if async_tasks:
        await self._poll_async_tasks(handler, nodes, async_tasks)
```

### **异步任务轮询**

```python
async def _poll_async_tasks(self, handler, nodes, async_tasks):
    node_map = {n.node_id: n for n in nodes}
    
    max_retries = poll_max_retries  # 默认轮询 1 次，可配置
    retry_count = 0
    
    while async_tasks and retry_count < max_retries:
        # 批量查询
        task_ids = list(async_tasks.values())
        results = await handler.poll_tasks(task_ids)
        
        # 处理结果
        for node_id, task_id in list(async_tasks.items()):
            if task_id not in results:
                continue
            
            result = results[task_id]
            node = node_map[node_id]
            
            if result.status == TaskStatus.SUCCESS:
                node.status = SyncStatus.SUCCESS
                if result.data_id:
                    node.data_id = result.data_id
                async_tasks.pop(node_id)
                
            elif result.status == TaskStatus.FAILED:
                node.status = SyncStatus.FAILED
                node.error = result.error
                async_tasks.pop(node_id)
        
        # 还有未完成任务，等待后重试
        if async_tasks:
            await asyncio.sleep(2)
            retry_count += 1
    
    # 超时未完成的任务标记为失败
    for node_id in async_tasks:
        node = node_map[node_id]
        node.status = SyncStatus.FAILED
        node.error = "Async task timeout"
```

---

## 完整流程示例

### **Pipeline 编排**

```python
# Stage 0: 初始化
collection = DataCollection()
datasource = ApiDataSource(api_client)

# 注册 Handler
datasource.register_handler(ProjectNodeHandler(api_client))
datasource.register_handler(ContractNodeHandler(api_client))

# Stage 1: 加载数据
datasource.set_collection(collection)
await datasource.load_all(
    order=["project", "contract"]  # 依赖顺序
)

# Stage 2: Strategy 执行
for strategy in [project_strategy, contract_strategy]:
    await strategy.bind()        # 设置 binding_status
    await strategy.create()      # 设置 action=CREATE + ID 映射
    await strategy.update()      # 设置 action=UPDATE + ID 映射
    await strategy.pre_sync_check()

# Stage 3: 物理同步
await datasource.sync_all(
    order=["project", "contract"]
)

# Stage 4: 检查结果
summary = datasource.get_sync_summary(collection)
print(f"Success: {summary['success']}, Failed: {summary['failed']}")
```

### **Contract Handler 实现**

```python
class ContractNodeHandler(BaseNodeHandler[ContractResponse]):
    def __init__(self, api_client):
        super().__init__(
            node_type="contract",
            node_class=ContractSyncNode,
            schema=ContractResponse
        )
        self.api = api_client
    
    async def load(self):
        """加载合同（依赖 Project）"""
        if self._collection:
            projects = self._collection.filter(node_type="project")
            project_ids = [p.data_id for p in projects if p.data_id]
        else:
            project_ids = []
        
        response = await self.api.get_contracts(project_ids=project_ids)
        return response["data"]
    
    async def create(self, node):
        """创建合同（异步接口）"""
        try:
            # node.data 已经过 ID 映射，直接使用
            response = await self.api.create_contract_async(node.data)
            
            if response.get("async"):
                return TaskResult.in_progress(task_id=response["task_id"])
            else:
                return TaskResult.success(data_id=response["id"])
                
        except Exception as e:
            return TaskResult.failed(error=str(e))
    
    async def update(self, node):
        """更新合同（同步接口）"""
        try:
            response = await self.api.update_contract(node.data_id, node.data)
            return TaskResult.success()
        except Exception as e:
            return TaskResult.failed(error=str(e))
    
    async def poll_tasks(self, task_ids):
        """轮询异步任务"""
        response = await self.api.batch_query_tasks(task_ids)
        results = {}
        for task in response["tasks"]:
            if task["status"] == "completed":
                results[task["id"]] = TaskResult.success(
                    data_id=task["result"]["contract_id"]
                )
            elif task["status"] == "failed":
                results[task["id"]] = TaskResult.failed(error=task["error"])
            else:
                results[task["id"]] = TaskResult.in_progress(task_id=task["id"])
        return results
    
    async def validate(self, data):
        """数据质量检查"""
        errors = []
        if not data.get("code"):
            errors.append("合同编码为空")
        if not data.get("project_id"):
            errors.append("项目 ID 为空")
        
        return ValidationResult(is_valid=len(errors) == 0, errors=errors)
    
    def extract_id(self, data):
        return data["id"]
```

---

## 核心优势

### **1. 职责清晰**

- **Strategy**: 业务逻辑 + ID 映射
- **DataSource**: 状态管理 + 编排
- **Handler**: 纯执行（后端 API 调用）

### **2. ID 映射前置**

- ID 映射在 Strategy 完成，DataSource 和 Handler 无需关心
- `node.data` 已经是映射好的数据，直接使用

### **3. 状态统一管理**

- 所有状态流转由 DataSource 负责
- Handler 只返回结果，不改变节点状态

### **4. 异步任务支持**

- Handler 返回 `TaskResult.in_progress(task_id)`
- DataSource 自动轮询直到完成

### **5. 数据质量保障**


- Handler 提供 `validate()` 方法
- DataSource 在加载时检查数据质量

### **6. 易于测试**

- Handler 可以 Mock（返回假 TaskResult）
- DataSource 可以单独测试状态流转逻辑
- Strategy 可以单独测试 ID 映射逻辑

---

## 与现有架构的兼容性

完全符合 [架构分层设计.md](./架构分层设计.md) 的四层模型：

1. **Common 层**: SyncNode, DataCollection, BindingManager
2. **Sync System 层**: SyncStrategy（负责 bind/create/update + ID 映射）
3. **DataSource 层**: BaseDataSource + NodeHandler（状态管理 + 执行）
4. **Pipeline 层**: 流程编排 + 依赖顺序

符合 [状态定义规范.md](./状态定义规范.md) 的状态流转逻辑。
