# API DataSource 架构设计

## 架构总览

```
┌─────────────────────────────────────────────────────────────┐
│                       Pipeline                               │
│                  (同步流程编排)                               │
└───────────────────────────┬─────────────────────────────────┘
                            │
                            ▼
┌─────────────────────────────────────────────────────────────┐
│                    ApiDataSource                             │
│                  (数据源协调层)                               │
│                                                               │
│  - 持有 ApiClient 实例                                       │
│  - 注册和管理 Handler                                        │
│  - 实现 load_all / execute_batch                            │
│  - 注入 api_client 到 Handler                               │
└───────────────────────────┬─────────────────────────────────┘
                            │
        ┌───────────────────┼───────────────────┐
        ▼                   ▼                   ▼
┌──────────────┐    ┌──────────────┐    ┌──────────────┐
│   Handler    │    │   Handler    │    │   Handler    │
│   Project    │    │  Contract    │    │   Material   │
│              │    │              │    │              │
│ 业务逻辑层   │    │ 业务逻辑层   │    │ 业务逻辑层   │
└──────┬───────┘    └──────┬───────┘    └──────┬───────┘
       │                   │                   │
       └───────────────────┼───────────────────┘
                           │ 使用 self.api_client
                           ▼
                    ┌──────────────┐
                    │  ApiClient   │
                    │ (HTTP 通信层) │
                    │              │
                    │ - 签名生成   │
                    │ - HTTP 请求  │
                    │ - Push Log   │
                    └──────────────┘
```

**调用流程**：
1. Pipeline 创建 ApiDataSource，传入 base_url, uid, secret
2. ApiDataSource 内部创建 ApiClient 实例
3. ApiDataSource 注册各业务 Handler，并注入 api_client
4. Handler 使用 `self.api_client.get/post/put()` 发送请求
5. ApiClient 自动处理签名、push_log 查询、错误处理

**优势**：
- ✅ **职责清晰**：ApiClient（通信）、DataSource（协调）、Handler（业务）
- ✅ **避免循环依赖**：Handler → ApiClient（单向依赖）
- ✅ **易于测试**：ApiClient 可 mock，Handler 独立测试
- ✅ **易于扩展**：新业务只需实现 Handler

## 1. 问题分析：旧方案的复杂性

旧方案 `sync_system/datasource/api` 存在的问题：

1. **过度抽象**：引入了 `ApiDataSource` → `Repository` → `ResponseHandle` → `Cache` 多层封装，每个业务需要独立 Repository 类，Protocol 定义繁琐
2. **状态管理混乱**：缓存策略不清晰，ResponseHandle 封装异步结果但使用困难，缺少统一的 push_log 查询机制
3. **依赖关系困难**：Repository 之间互相传递引用，难以从 collection 获取已有数据
4. **API 路由分散**：每个 Repository 手动实现多个 update 方法，缺少自动路由机制

## 2. 新方案设计原则

- **简单直接**：Handler 直接调用 HTTP API，不过度封装
- **状态透明**：DataSource 统一管理 session、签名、push_log 查询
- **依赖清晰**：通过 collection 获取依赖数据，使用 context 存储额外信息
- **智能路由**：根据数据字段自动选择 API endpoint

## 3. 核心组件职责

### 3.1 ApiClient - HTTP 通信层

**职责**：
- 封装所有 HTTP 通信逻辑
- 管理 httpx.AsyncClient，维护连接池和 session
- 生成请求签名（timestamp, nonce, sign）
- 统一请求入口，自动注入 uid
- 批量 Push Log 查询

**核心方法**：
```python
class ApiClient:
    def __init__(self, base_url: str, uid: str, secret: str):
        self._client = httpx.AsyncClient()
        self._base_url = base_url
        self._uid = uid
        self._secret = secret
    
    async def request(self, method: str, url: str, **kwargs) -> httpx.Response
        """统一请求入口，自动签名"""
    
    async def get(self, url: str, params: dict = None) -> httpx.Response
        """GET 请求"""
    
    async def post(self, url: str, data: dict) -> httpx.Response
        """POST 请求，data 中已包含 push_id/biz_id（由 Handler 生成）"""
    
    async def put(self, url: str, data: dict) -> httpx.Response
        """PUT 请求，data 中已包含 push_id/biz_id"""
    
    async def delete(self, url: str, data: dict) -> httpx.Response
        """DELETE 请求，data 中已包含 push_id/biz_id"""
    
    async def get_push_logs(self, push_ids: List[str]) -> Dict[str, dict]
        """批量查询 push_log 状态，返回 {push_id: push_log_data}
        注意：单个查询也传单元素列表
        当前实现：用 for 循环模拟，后续等后端实现批量接口
        """
    
    def _generate_signature(self, params: dict) -> dict
        """生成签名参数（timestamp, nonce, sign）"""
```

### 3.2 ApiDataSource - 数据源协调层

**职责**：
- 持有 ApiClient 实例，提供给 Handler 使用
- 实现 DataSource 接口（load_all, sync_all）
- 协调多个 Handler 的执行顺序（通过 sync_all）
- Handler 内部负责：生成 push_id/biz_id、调用 poll 轮询、提取 created_id
- 统一异常处理和日志记录

**核心属性和方法**：
```python
class ApiDataSource(DataSource):
    def __init__(self, base_url: str, uid: str, secret: str):
        self.client = ApiClient(base_url, uid, secret)
        self._handlers: Dict[str, BaseApiHandler] = {}
    
    def register_handler(self, node_type: str, handler: BaseApiHandler):
        """注册业务 Handler"""
        handler.api_client = self.client  # 注入 client
        self._handlers[node_type] = handler
    
    async def load_all(self, collection: DataCollection, order: List[str]) -> None:
        """调用基类的 load_all，按顺序加载数据"""
    
    async def sync_all(self, collection: DataCollection, order: List[str]) -> None:
        """调用基类的 sync_all，按顺序执行同步"""
```

### 3.3 BaseApiHandler - 业务逻辑基类

**职责**：
- 实现 Handler 接口（load, create, update, delete, poll）
- 使用 self.api_client 发送 HTTP 请求
- 生成 push_id 和 biz_id（业务标识）
- 从 collection 获取依赖节点数据
- 管理节点的 context（存储 project_id, contract_id 等）
- 实现 poll 方法，调用 api_client.get_push_logs 批量查询
- 提取 created_id（因为不同业务的 id 字段名和位置可能不同）
- 返回 TaskResult（SUCCESS/FAILED/IN_PROGRESS）

**核心方法**：
```python
class BaseApiHandler(NodeHandler):
    def __init__(self):
        self.api_client: ApiClient = None  # 由 DataSource 注入
        self.collection: DataCollection = None  # 由 DataSource 注入
    
    async def load(self, collection: DataCollection) -> List[dict]
    async def create(self, node: SyncNode) -> TaskResult
    async def update(self, node: SyncNode) -> TaskResult
    async def delete(self, node: SyncNode) -> TaskResult
    async def poll(self, node: SyncNode, task_id: str) -> TaskResult
    
    def extract_created_id(self, push_log: dict) -> str
        """从 push_log 提取 created_id，子类可重写"""
    
    # 重试逻辑（可选，子类可重写）
    async def _execute_with_retry(self, operation, max_retries: int = 3) -> Any
        """通用重试逻辑，子类可自定义重试策略"""
```

**重试策略说明**：
- **重试在 Handler 内部**：create/update/delete 内部可调用 _execute_with_retry
- **返回给 DataSource**：最终只返回 SUCCESS/FAILED/IN_PROGRESS
- **灵活控制**：不同业务可重写 _execute_with_retry 实现自定义策略
- **默认策略**：网络错误/5xx 重试 3 次，4xx 不重试

**未实现 API 的处理**：
- 部分业务可能没有实现 create 或 delete API
- 此时 Handler 应在对应方法中直接返回 FAILED 状态，并在 error 中说明原因
- 示例：`return TaskResult(status=TaskStatus.FAILED, error="Create API not implemented for this business")`

### 3.4 具体业务 Handler

**职责**：
- 实现具体业务的 load 逻辑（调用 self.api_client.get()）
- 创建节点时设置 context
- 重写 API endpoint 选择逻辑（可选）
- 定制 created_id 提取逻辑（可选）

每个业务 Handler 位于 `sync_system_new/domain/{业务}/api_handler.py`

## 4. HTTP 请求状态管理

### 4.0 状态系统概述

**通用状态枚举**（适用于所有 DataSource）：

```python
class TaskStatus(Enum):
    """Handler 返回的任务执行状态"""
    SUCCESS = "success"          # 同步完成，立即成功
    FAILED = "failed"            # 同步失败
    IN_PROGRESS = "in_progress"  # 异步任务进行中

class SyncStatus(Enum):
    """SyncNode 的同步执行状态"""
    PENDING = "PENDING"
    IN_PROGRESS = "IN_PROGRESS"
    SUCCESS = "SUCCESS"
    FAILED = "FAILED"
    SKIPPED = "SKIPPED"
    PRECONDITION_FAILED = "PRECONDITION_FAILED"
```

**状态映射规则**：
- TaskStatus 是 Handler 执行操作后的返回值
- SyncStatus 是 SyncNode 的最终状态，由 DataSource 根据 TaskStatus 更新
- 普通 DataSource（FileDataSource）和 API DataSource 使用相同的状态枚举
- 区别在于：**API DataSource 的网络操作如何转化为 TaskStatus**

**重要说明：Load 阶段 vs 执行阶段的状态**：
- **Load 阶段**（Handler.load()）：从 API 拉取数据创建 SyncNode
  - 此时节点状态为 `PENDING`（等待后续 bind/create/update）
  - GET 请求失败不影响节点创建，只是该节点不存在于 collection
- **执行阶段**（Handler.create/update/delete）：执行同步操作
  - DataSource 在调用前设置 `node.status = IN_PROGRESS`
  - Handler 返回 TaskResult，DataSource 据此更新 node.status
  - POST/PUT/DELETE 成功后才设置为 SUCCESS

**核心映射逻辑**（在 DataSource 中）：
```python
# 执行前：DataSource 设置状态
node.status = SyncStatus.IN_PROGRESS

# 调用 Handler（Handler 内部可能重试多次）
result = await handler.create(node)

# 执行后：DataSource 根据最终结果更新状态
if result.status == TaskStatus.SUCCESS:
    node.status = SyncStatus.SUCCESS
    node.data_id = result.data_id
elif result.status == TaskStatus.FAILED:
    node.status = SyncStatus.FAILED
    node.error = result.error
elif result.status == TaskStatus.IN_PROGRESS:
    # 记录 task_id 等待轮询
    async_tasks[node.node_id] = result.task_id
```

**重要**：Handler 负责重试，最终只返回确定的结果（SUCCESS/FAILED/IN_PROGRESS）

### 4.1 请求类型与状态流转

**API DataSource 的特殊性**：
- **Load 阶段**：GET 请求拉取数据，创建 PENDING 节点（失败则不创建节点）
- **执行阶段**：POST/PUT/DELETE 先设置 IN_PROGRESS，通过 poll() 轮询变为 SUCCESS/FAILED

#### Load 阶段 - 数据拉取

**目的**：从 API 获取数据，创建 SyncNode 放入 collection

| 阶段 | HTTP 操作 | 成功行为 | 失败行为 | 节点状态 |
|------|----------|---------|---------|---------|
| 1. 调用 load() | GET /api/list | 返回数据列表 | 返回空列表或抛异常 | - |
| 2. 创建节点 | - | 为每条数据创建 SyncNode | 不创建节点 | PENDING |

**代码示例**：
```python
async def load(self, collection: DataCollection) -> List[dict]:
    """加载数据 - GET 阶段不涉及状态转换"""
    try:
        response = await self.api_client.get("project/list")
        if response.status_code == 200:
            data_list = response.json().get("list", [])
            # 返回数据，由 DataSource 创建 PENDING 节点
            return data_list
        else:
            # 失败：返回空列表，不创建节点
            logger.error(f"Load failed: {response.status_code}")
            return []
    except Exception as e:
        logger.error(f"Load exception: {e}")
        return []  # 或者抛出异常，由调用方处理
```

#### Mutation 请求（POST/PUT/DELETE） - 执行阶段

**状态变化时机**：
1. **执行前**：DataSource 设置 `node.status = IN_PROGRESS`
2. **Handler 执行**：内部可能重试，最终返回 SUCCESS/FAILED/IN_PROGRESS
3. **轮询完成**：poll() 返回 SUCCESS/FAILED

| 阶段 | Push Log | TaskStatus | SyncStatus | 说明 |
|------|---------|-----------|-----------|------|
| 执行前 | - | - | PENDING → IN_PROGRESS | DataSource 设置 |
| 提交 | pending | IN_PROGRESS | IN_PROGRESS | 任务已提交 |
| 轮询 | processing | IN_PROGRESS | IN_PROGRESS | 后端处理中 |
| 成功 | success | SUCCESS | SUCCESS | 同步完成 |
| 失败 | failed | FAILED | FAILED | 同步失败 |

**示例**：
```python
async def create(self, node: SyncNode) -> TaskResult:
    """Handler 内部可重试，最终返回确定结果"""
    response = await self.api_client.post(url, node.data)
    if response.status_code == 202:
        return TaskResult.in_progress(task_id=response.json()["push_id"])
    else:
        return TaskResult.failed(error=f"HTTP {response.status_code}")

async def poll(self, node: SyncNode, task_id: str) -> TaskResult:
    push_logs = await self.api_client.get_push_logs([task_id])
    status = push_logs.get(task_id, {}).get("status")
    if status == "success":
        return TaskResult.success(data_id=self.extract_created_id(push_logs[task_id]))
    elif status == "failed":
        return TaskResult.failed(error="Backend processing failed")
    else:
        return TaskResult.in_progress(task_id=task_id)
```

### 4.2 状态转换完整流程

```
Load 阶段:
  Handler.load() → 返回数据列表 → DataSource 创建 PENDING 节点

执行阶段（同步操作）:
  DataSource: node.status = IN_PROGRESS
  → Handler.create() → TaskResult.success/failed()
  → DataSource: node.status = SUCCESS/FAILED

执行阶段（异步操作）:
  DataSource: node.status = IN_PROGRESS
  → Handler.create() → TaskResult.in_progress(push_id)
  → DataSource 轮询: Handler.poll(push_id)
  → TaskResult.success/failed()
  → DataSource: node.status = SUCCESS/FAILED
```

**关键点**：
- Handler 负责重试，DataSource 只处理最终结果
- 不同业务可在 BaseApiHandler._execute_with_retry 中自定义重试策略
- FileDataSource 和 ApiDataSource 使用相同的状态枚举和转换逻辑

### 4.3 Push Log 状态机

```
pending → processing → success/failed
```

- 轮询间隔：0.5秒
- 超时：30秒
- 成功：提取 created_id
- 失败：获取 error_message

### 4.4 状态总结表

**Load 阶段（数据拉取）**：

| HTTP 操作 | 响应码 | 行为 | 创建节点 | 节点状态 | 说明 |
|---------|-------|-----|---------|---------|------|
| GET /list | 200 | 返回数据列表 | ✅ 是 | PENDING | 等待后续 bind/create/update |
| GET /list | 4xx/5xx | 返回空列表 | ❌ 否 | - | 不创建节点 |

**执行阶段（POST/PUT/DELETE）**：

| HTTP 操作 | 响应码 | Push Log | TaskStatus | SyncStatus | 说明 |
|---------|-------|---------|-----------|-----------|------|
| POST/PUT | 202 | pending | IN_PROGRESS | IN_PROGRESS | 异步开始 |
| - | - | processing | IN_PROGRESS | IN_PROGRESS | 异步处理中 |
| - | - | success | SUCCESS | SUCCESS | 异步成功 |
| - | - | failed | FAILED | FAILED | 异步失败 |
| - | - | timeout | FAILED | FAILED | 轮询超时 |

## 5. Context 机制

### 5.1 Context 的作用

Context 是 SyncNode 的字典字段，用于存储业务数据之外的上下文信息，例如：
- `project_id`: 当前对象所属的项目ID
- `contract_id`: 合同变更所属的合同ID  
- `parent_id`: 父对象ID
- 其他 API 调用需要但 schema 中没有的信息

### 5.2 Context 使用流程

**场景：加载合同数据**

1. **Handler.load()** 从 self._collection 获取所有 project
2. 遍历每个 project，调用 `GET contract/list?project_id=xxx`
3. 为每个返回的 contract 数据添加临时标记 `_context = {"project_id": xxx}`
4. **Handler.create_node()** 创建 SyncNode 时，提取 `_context` 并设置到 `node.context`
5. **Handler.update()** 更新时，从 `node.context["project_id"]` 获取 project_id 并添加到请求数据

### 5.3 Context 传递规则

- 父 → 子：从依赖的父节点继承 context
- 横向传递：同级节点不共享 context
- 更新时使用：从 context 提取必要信息添加到请求 payload

## 6. 智能 API 路由

### 6.1 路由策略

根据节点数据的字段，自动选择正确的 API endpoint：

**方案1：字段匹配**
- 检查 node.data 中的字段集合
- 匹配预定义的 endpoint → required_fields 映射表
- 选择第一个匹配的 endpoint

**方案2：Schema 类型**
- 根据 node.schema 的类型
- 查找 schema_type → endpoint 映射表

**默认规则**：
- CREATE: `{node_type}`
- UPDATE: 子类重写 `_select_update_endpoint()`
- DELETE: `{node_type}`

### 6.2 Project 示例

Project 有多个更新接口：
- `project/key_constraints` - 需要字段：key_constraints, key_constraints_remark
- `project/complete_investment` - 需要字段：complete_investment
- `project/dynamic_investment` - 需要字段：dynamic_investment
- `project/plan_construction` - 需要字段：plan_construction_capacity, plan_construction_date
- ... 等

Handler 定义映射表，根据实际数据字段自动选择。

## 7. 依赖数据获取

### 7.1 获取模式

Handler 通过 `collection.filter(node_type="xxx")` 获取依赖数据：

**加载顺序**（按依赖关系）：
1. Project（无依赖）
2. Contract（依赖 Project）
3. Contract Change（依赖 Project + Contract）

### 7.2 数据流

```
Pipeline
  → 按拓扑序加载
  → ProjectHandler.load()
      → GET project/list
      → 创建 ProjectSyncNode
  → ContractHandler.load()  
      → collection.filter(node_type="project")
      → 遍历 project，GET contract/list?project_id=xxx
      → 创建 ContractSyncNode，设置 context["project_id"]
  → ContractChangeHandler.load()
      → collection.filter(node_type="contract")
      → 遍历 contract，从 context 获取 project_id
      → GET contract_change/list?project_id=xxx&contract_id=yyy
      → 创建节点，设置 context
```

## 8. 实现计划

### Phase 1: HTTP 通信层
- 实现 **ApiClient** 类
  - HTTP Client 封装（httpx.AsyncClient）
  - 签名生成（_generate_signature）
  - 统一请求方法（get/post/put/delete）
  - **批量 Push Log 查询**（get_push_logs，单个也传列表）
  - ~~wait_for_push_log~~（不实现，由 DataSource 调用 poll）
  
### Phase 2: DataSource 协调层
- 实现 **ApiDataSource** 类
  - 持有 ApiClient 实例
  - Handler 注册和管理
  - 实现 DataSource 接口（load_all, execute_batch）
  - 注入 api_client 到 Handler
  - 实现轮询逻辑（_poll_async_tasks）

### Phase 3: Handler 基类
- 实现 **BaseApiHandler** 类
  - load/create/update/delete/poll 方法
  - 使用 self.api_client 发送请求
  - **extract_created_id**（子类可重写）
  - **_execute_with_retry**（可选，子类可重写重试策略）
  - Context 管理

### Phase 4: 业务验证
- 实现 **ProjectApiHandler**（无依赖，简单场景）
  - 实现 load：GET project/list
  - 实现 create：POST project
  - 实现 poll：查询 push_log
- 实现 **ContractApiHandler**（依赖 Project，验证 context）
  - 实现 load：遍历 project，GET contract/list?project_id=xxx
  - 设置 context["project_id"]
- 编写测试验证设计

### Phase 5: 完善功能
- 批量 Push Log 查询优化（等待后端实现批量接口）
- 超时控制（可配置）
- 请求日志和调试

### Phase 6: 推广
- 实现剩余业务 Handler
- 性能优化（并发控制）
- 文档和示例

## 9. 与现有系统集成

**调用关系**：
```
Pipeline
    ↓
ApiDataSource (持有 ApiClient)
    ↓
BaseApiHandler (使用 api_client)
    ↓
ApiClient (发送 HTTP 请求)
```

**职责分离**：
- **ApiClient**: 纯粹的 HTTP 通信，无业务逻辑
- **ApiDataSource**: 协调 Handler，管理执行流程
- **BaseApiHandler**: 业务逻辑，调用 ApiClient 完成网络操作
- **具体 Handler**: 实现特定业务的 load/create/update/delete

**优点**：
- ApiClient 和 Handler 解耦，避免循环依赖
- Handler 通过 self.api_client 访问 HTTP 功能
- DataSource 统一管理 ApiClient 生命周期
- Handler 内部灵活控制重试策略

## 10. 关键优势

1. **简化架构**：去掉 Repository 层，代码量减少 50%
2. **职责清晰**：
   - ApiClient：HTTP 通信
   - DataSource：协调和轮询
   - Handler：业务逻辑和重试
3. **重试灵活**：Handler 内部实现，不同业务可自定义策略
4. **状态清晰**：Load 阶段创建 PENDING 节点，执行阶段明确状态转换
5. **批量优化**：Push Log 批量查询，减少 HTTP 请求
6. **易于扩展**：新业务只需实现 Handler，复用 Client 和 DataSource
7. **易于测试**：ApiClient 可 mock，Handler 独立测试

## 11. 设计改进总结

| 改进点 | 初始设计 | 最终设计 | 理由 |
|-------|---------|---------|------|
| Push Log 查询 | get_push_log(id) | get_push_logs([ids]) | 批量查询 |
| 轮询逻辑 | wait_for_push_log 在 Client | 移除，DataSource 调用 poll | 职责清晰 |
| created_id 提取 | 在 Client | 在 Handler 基类 | 业务差异 |
| 重试逻辑 | 在 DataSource | **在 Handler** | 业务灵活性 |
| Load 状态 | GET 成功 → SUCCESS | GET 成功 → PENDING | Load ≠ 同步完成 |
| 执行状态 | 不明确 | 执行前设置 IN_PROGRESS | 状态清晰 |
7. **易于测试**：ApiClient 可以 mock，Handler 单独测试业务逻辑

## 12. 使用示例

### 12.1 基本使用流程

```python
# 1. 创建 DataSource
datasource = ApiDataSource(
    base_url="https://api.example.com",
    uid="user123",
    secret="secret_key"
)

# 2. 初始化（必须调用）
await datasource.initialize()

# 3. 注册 Handler
datasource.register_handler(ProjectApiHandler())
datasource.register_handler(ContractApiHandler())

# 4. 创建 Collection
collection = DataCollection()

# 5. 加载数据（自动处理异常，不会抛出）
await datasource.load_all(collection, order=["project", "contract"])

# 6. 执行同步（自动处理异常，不会抛出）
await datasource.sync_all(collection, order=["project", "contract"])

# 7. 关闭连接（必须调用）
await datasource.close()
```

### 12.2 异常处理说明

**设计原则**：
- ✅ **load_all 和 sync_all 不会抛出异常**
- ✅ **Handler 的 create/update/delete 捕获所有 HTTP 异常**
- ✅ **所有错误转换为状态（SyncStatus.FAILED）和错误信息（node.error）**

**为什么不需要 try-finally？**

```python
# ❌ 不需要这样做
try:
    await datasource.load_all(collection, order=["project"])
finally:
    await datasource.close()

# ✅ 直接调用即可
await datasource.initialize()
await datasource.load_all(collection, order=["project"])  # 内部已处理异常
await datasource.sync_all(collection, order=["project"])  # 内部已处理异常
await datasource.close()
```

**异常处理机制**：

1. **load_all 异常处理**：
   ```python
   # 加载某个类型失败，记录错误但继续处理其他类型
   for node_type in order:
       try:
           raw_items = await handler.load(collection)
           # ... 创建节点
       except Exception as e:
           print(f"Error loading {node_type}: {e}")
           # 继续处理下一个类型
   ```

2. **sync_all 异常处理**：
   ```python
   # Handler 返回的异常已被捕获转为 TaskResult
   for node in nodes:
       try:
           result = await handler.create(node)  # Handler 内部捕获异常
           # 根据 result.status 更新节点状态
       except Exception as e:
           # DataSource 的最后保护
           node.status = SyncStatus.FAILED
           node.error = str(e)
   ```

3. **Handler 异常处理**：
   ```python
   async def create(self, node: SyncNode) -> TaskResult:
       try:
           # 生成参数
           push_id = self._generate_push_id()
           data = {..., "push_id": push_id, "biz_id": self._generate_biz_id(node)}
           
           # 调用 API
           response = await self.api_client.post("/api/v2/project", data)
           
           # 返回成功
           return TaskResult(status=TaskStatus.SUCCESS, data_id=response['data']['id'])
           
       except httpx.HTTPStatusError as e:
           # HTTP 错误（4xx, 5xx）
           return TaskResult(status=TaskStatus.FAILED, error=f"HTTP {e.response.status_code}: {e}")
       except httpx.NetworkError as e:
           # 网络错误
           return TaskResult(status=TaskStatus.FAILED, error=f"Network error: {e}")
       except Exception as e:
           # 其他异常
           return TaskResult(status=TaskStatus.FAILED, error=f"Unexpected error: {e}")
   ```

### 12.3 Pipeline 集成示例

```python
async def sync_pipeline(datasource: ApiDataSource):
    """同步流程编排"""
    
    # 初始化
    await datasource.initialize()
    
    # 创建 Collection
    collection = DataCollection()
    
    # 执行同步流程（所有错误都被内部处理）
    await datasource.load_all(collection, order=["project", "contract", "material"])
    await datasource.sync_all(collection, order=["project", "contract", "material"])
    
    # 检查结果
    failed_nodes = collection.filter(status=SyncStatus.FAILED)
    if failed_nodes:
        print(f"Failed nodes: {len(failed_nodes)}")
        for node in failed_nodes:
            print(f"  - {node.node_type}:{node.node_id}: {node.error}")
    
    # 清理
    await datasource.close()
```

**关键点**：
- ✅ 不需要 try-except 包裹 load_all/sync_all
- ✅ 通过检查节点状态判断成功/失败
- ✅ 错误信息保存在 node.error 中
- ✅ Pipeline 专注于流程编排，不处理异常细节

