first commit
This commit is contained in:
@@ -0,0 +1,492 @@
|
||||
# 节点生命周期状态转移 (Node Lifecycle State Transitions)
|
||||
|
||||
本文档从一个**单节点视角**出发,完整描述一个 `SyncNode` 从诞生到持久化的全部状态变化。
|
||||
|
||||
> **代码映射**:
|
||||
> - 持久化重置:`sync_system_new/common/state_reset.py` → `NodeStateReset`
|
||||
> - 绑定判定:`sync_system_new/sync_system/decision.py` → `SyncDecisionEngine`
|
||||
> - 策略执行:`sync_system_new/sync_system/strategy.py` → `DefaultSyncStrategy`
|
||||
> - ID 解析:`sync_system_new/sync_system/resolve_ids.py` → `IDResolver`
|
||||
> - 物理同步:`sync_system_new/datasource/datasource.py` → `BaseDataSource`
|
||||
> - Handler 降级:`sync_system_new/domain/*/api_handler.py`
|
||||
|
||||
---
|
||||
|
||||
## 状态字段一览
|
||||
|
||||
| 字段 | 类型 | 含义 | 谁负责设置 |
|
||||
|------|------|------|-----------|
|
||||
| `binding_status` | BindingStatus | 绑定健康状态 | Strategy.bind() |
|
||||
| `action` | SyncAction | 待执行动作 | Strategy.create() / update() |
|
||||
| `status` | SyncStatus | 执行结果状态 | DataSource.sync_all() |
|
||||
| `data_id` | str | 业务主键ID | 数据加载 / DataSource 回填 |
|
||||
| `error` | str \| None | 错误信息 | 各阶段 |
|
||||
|
||||
---
|
||||
|
||||
## 完整生命周期
|
||||
|
||||
### 第一次运行(无持久化数据)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ 节点尚不存在 │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
数据源加载 (Stage 1)
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ 节点诞生 │
|
||||
│ │
|
||||
│ binding_status = UNCHECKED ← Collection 创建节点时的初始值 │
|
||||
│ action = NONE │
|
||||
│ status = PENDING │
|
||||
│ data_id = "abc-123" ← 从数据源获取的业务主键 │
|
||||
│ data = {...} ← 从数据源获取的业务数据 │
|
||||
│ origin_data = {...} ← 同上,原始快照 │
|
||||
│ depend_ids = [] ← 空,待 bind 阶段填充 │
|
||||
│ error = None │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 第二次及后续运行(有持久化数据)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ 从持久化恢复(原始值) │
|
||||
│ │
|
||||
│ binding_status = NORMAL (上次值) │
|
||||
│ action = UPDATE (上次值) │
|
||||
│ status = SUCCESS (上次值) │
|
||||
│ data_id = "abc-123" │
|
||||
│ error = None (上次值) │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
阶段1:加载时重置 (NodeStateReset)
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ 阶段1重置后 │
|
||||
│ │
|
||||
│ binding_status = UNCHECKED ← 重置(每次重新判定) │
|
||||
│ action = UPDATE ← 保留!(用于阶段2识别 CREATE 失败) │
|
||||
│ status = SUCCESS ← 保留!(不自动重置) │
|
||||
│ data_id = "abc-123" ← 不变 │
|
||||
│ data = None ← 重置(等待数据源覆盖) │
|
||||
│ origin_data = None ← 重置(等待数据源覆盖) │
|
||||
│ error = None ← 清空(避免上次错误干扰) │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
阶段2:CREATE 僵尸清理
|
||||
(只影响 action=CREATE 且 status=FAILED 或 data_id="" 的节点)
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ 阶段2清理后(正常节点) │
|
||||
│ │
|
||||
│ action = NONE ← 全部重置为 NONE,准备进入策略阶段 │
|
||||
│ 其他字段不变 │
|
||||
│ │
|
||||
│ (若为 CREATE 僵尸节点:直接从 Collection 删除 + 解除绑定) │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
数据源加载覆盖 (Stage 1 后半)
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ 数据源加载后 │
|
||||
│ │
|
||||
│ data = {...} ← 数据源最新数据覆盖 │
|
||||
│ origin_data = {...} ← 数据源最新数据覆盖 │
|
||||
│ 其他字段不变 │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Bind 阶段状态转移 (Stage 2.1)
|
||||
|
||||
Bind 分三个子阶段顺序执行,每个子阶段只修改 `binding_status`。
|
||||
|
||||
### Phase 1: 核心状态判定
|
||||
|
||||
> **代码**:`strategy_ops/bind_ops.py::phase_core_state()` → `SyncDecisionEngine.determine_core_status()`
|
||||
|
||||
```
|
||||
输入:binding_status = UNCHECKED
|
||||
|
||||
┌─ 有绑定记录?
|
||||
│
|
||||
┌─────┤
|
||||
│ YES │
|
||||
│ └─── 本地数据 + 远程数据 都健康?
|
||||
│ │
|
||||
│ ┌────┴────┐
|
||||
│ │ YES │ NO
|
||||
│ ▼ ▼
|
||||
│ NORMAL WARNING / ABNORMAL
|
||||
│
|
||||
│ NO
|
||||
└──────────────► MISSING
|
||||
```
|
||||
|
||||
| 输入条件 | binding_status 输出 |
|
||||
|---------|-------------------|
|
||||
| 有绑定,双方数据健康 | **NORMAL** |
|
||||
| 有绑定,自身数据损坏 | **ABNORMAL** |
|
||||
| 有绑定,对方数据损坏 | **WARNING** |
|
||||
| 无绑定记录 | **MISSING** |
|
||||
|
||||
### Phase 2: 依赖检查
|
||||
|
||||
> **代码**:`strategy_ops/bind_ops.py::phase_dependency_check()` → `strategy_ops/bind_ops.py::check_dependencies_for_nodes()`
|
||||
|
||||
**仅处理 `binding_status = MISSING` 的节点**,其他状态跳过。
|
||||
|
||||
```
|
||||
输入:binding_status = MISSING
|
||||
|
||||
┌─ 有配置 depend_fields?
|
||||
│
|
||||
┌─────┤
|
||||
│ NO └──► 保持 MISSING(无依赖,直接通过)
|
||||
│
|
||||
│ YES ──► 逐一检查依赖节点:
|
||||
│ │
|
||||
│ ┌────┴────────────────────────┐
|
||||
│ │ 全部依赖节点: │
|
||||
│ │ - 存在? ✅ │
|
||||
│ │ - binding_status=NORMAL? ✅ │
|
||||
│ │ - data_id 非空? ✅ │
|
||||
│ └────┬────────────────────────┘
|
||||
│ │
|
||||
│ ┌────┴────┐
|
||||
│ │ 全通过 │ 任一失败
|
||||
│ ▼ ▼
|
||||
│ MISSING DEPENDENCY_ERROR
|
||||
│ + error = "依赖项不满足: ..."
|
||||
│ + depend_ids 已填充
|
||||
```
|
||||
|
||||
**副作用**:无论检查是否通过,都会填充 `node.depend_ids`。
|
||||
|
||||
### Phase 3: 自动绑定
|
||||
|
||||
> **代码**:`strategy_ops/bind_ops.py::phase_auto_binding()`
|
||||
|
||||
**仅处理 `binding_status = MISSING` 且依赖已通过的节点**。
|
||||
|
||||
```
|
||||
输入:binding_status = MISSING
|
||||
|
||||
auto_bind = False?
|
||||
└──► MISSING → WARNING("跳过自动绑定,无法确认是否需要创建")
|
||||
|
||||
auto_bind = True:
|
||||
│
|
||||
├─ 业务键字段缺失?
|
||||
│ └──► MISSING → WARNING("缺少业务键字段: ...")
|
||||
│
|
||||
├─ 业务键中 ID 字段解析失败?
|
||||
│ └──► MISSING → DEPENDENCY_ERROR("业务键中的ID字段解析失败")
|
||||
│
|
||||
├─ 去噪后 1:1 匹配?
|
||||
│ └──► MISSING → NORMAL(执行自动绑定,建立绑定记录)
|
||||
│
|
||||
├─ N:0(无远程匹配)?
|
||||
│ ├──► create_enabled = True → MISSING → NORMAL,并 spawn 目标 CREATE 节点(S06)
|
||||
│ └──► create_enabled = False → MISSING → WARNING(孤儿且不允许自动创建,需人工处理)
|
||||
│
|
||||
└─ N:M / 多对多 / 多对一?
|
||||
└──► MISSING → WARNING("存在多个候选节点,无法自动绑定")
|
||||
```
|
||||
|
||||
### Bind 阶段结束后可能的状态
|
||||
|
||||
| binding_status | 后续可执行的操作 |
|
||||
|---------------|----------------|
|
||||
| **NORMAL** | UPDATE(如有差异)|
|
||||
| **MISSING** | CREATE |
|
||||
| **ABNORMAL** | 无(需人工) |
|
||||
| **WARNING** | 无(阻止自动操作) |
|
||||
| **DEPENDENCY_ERROR** | 无(等待依赖修复) |
|
||||
|
||||
---
|
||||
|
||||
## Create 阶段状态转移 (Stage 2.2)
|
||||
|
||||
> **代码**:`create()` → `strategy_ops/create_ops.py::add_create_action()`
|
||||
|
||||
> **对齐说明(与状态机配置一致)**:
|
||||
> - create_prepare 的入口仍是源节点 `binding_status = MISSING`。
|
||||
> - 但当前配置下,`N:0 + create_enabled=true` 可以在自动绑定阶段直接把源节点转为 `NORMAL`,并 `emit spawn_target_node_state=S06`。
|
||||
> - 因此“触发创建”的入口有两条:
|
||||
> 1) auto_bind 分支(E08f)直接 spawn;
|
||||
> 2) create_prepare 分支(E09b)在 create 检查成功后 spawn。
|
||||
|
||||
**仅处理 `binding_status = MISSING` 的节点**。
|
||||
|
||||
```
|
||||
输入:源节点 binding_status = MISSING, action = NONE
|
||||
|
||||
┌─ 源节点数据为空?
|
||||
│ └──► 跳过,记录日志
|
||||
│
|
||||
├─ IDResolver.resolve_and_validate() 失败?
|
||||
│ └──► binding_status → DEPENDENCY_ERROR
|
||||
│ error = "创建时依赖 ID 未解析: ..."
|
||||
│
|
||||
└─ 成功 ──► 在目标 Collection 创建新节点
|
||||
│
|
||||
├─ 源节点:
|
||||
│ binding_status → NORMAL("从本地创建到远程")
|
||||
│ action 不变(仍为 NONE)
|
||||
│
|
||||
└─ 新建目标节点:
|
||||
binding_status = NORMAL
|
||||
action = CREATE
|
||||
status = PENDING
|
||||
data_id = ""(待远程返回)
|
||||
data = 已替换 ID 的数据副本
|
||||
│
|
||||
└─ 同时建立绑定记录:local_id ↔ remote_id
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Commit Creates — 物理同步 (Stage 2.3)
|
||||
|
||||
> **代码**:`DataSource.sync_all()` → Handler.create()
|
||||
|
||||
```
|
||||
输入:action = CREATE, status = PENDING
|
||||
|
||||
┌─ Handler 执行 API 调用
|
||||
│
|
||||
├─ 同步 API 成功(200/201)
|
||||
│ └──► status → SUCCESS
|
||||
│ data_id = "remote-xyz"(远程分配的 ID 回填)
|
||||
│ data["id"] = "remote-xyz"(主键字段回写)
|
||||
│
|
||||
├─ 异步 API 提交成功(202)
|
||||
│ └──► status → IN_PROGRESS
|
||||
│ context.task_id = "task-xxx"
|
||||
│ │
|
||||
│ └─ 轮询结果:
|
||||
│ ├─ 成功 → status → SUCCESS, data_id 回填
|
||||
│ ├─ 失败 → status → FAILED, error = "..."
|
||||
│ └─ 超时 → status → FAILED, error = "Timeout"
|
||||
│
|
||||
└─ API 失败(4xx/5xx)
|
||||
└──► status → FAILED
|
||||
error = "远程操作失败: ..."
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Update 阶段状态转移 (Stage 2.4)
|
||||
|
||||
> **代码**:`update()` → `strategy_ops/update_ops.py::add_update_action()`
|
||||
|
||||
> **对齐说明**:图里的 `E12a` 从 `S01` 出发并不矛盾。`S01` 约束的是“源节点”有 `data_id`;
|
||||
> `E12a` 检查的是“目标节点”是否有 `data_id`(guard: `target_has_data_id=false`),两者不是同一个对象。
|
||||
|
||||
**过滤条件**:`binding_status = NORMAL` 且 `status != FAILED`
|
||||
|
||||
```
|
||||
输入:源节点 binding_status = NORMAL, action = NONE
|
||||
|
||||
┌─ status == FAILED?
|
||||
│ └──► 跳过(保护上次失败的状态和错误信息)
|
||||
│
|
||||
├─ 源节点数据为空?
|
||||
│ └──► 跳过
|
||||
│
|
||||
├─ 通过绑定找不到目标节点?
|
||||
│ └──► 跳过,记录日志
|
||||
│
|
||||
├─ 目标节点 data_id 为空?
|
||||
│ └──► 目标节点 status → PRECONDITION_FAILED
|
||||
│ ("目标节点缺少 data_id")
|
||||
│
|
||||
├─ IDResolver.resolve_and_validate() 失败?
|
||||
│ └──► 目标节点 action → UPDATE
|
||||
│ 目标节点 status → PRECONDITION_FAILED
|
||||
│ 目标节点 error = "依赖 ID 未解析: ..."
|
||||
│
|
||||
├─ _needs_update() 判定无差异?
|
||||
│ └──► 跳过(不设置 action)
|
||||
│
|
||||
└─ 有差异 ──► 更新目标节点:
|
||||
action → UPDATE
|
||||
status → PENDING
|
||||
data = 已替换 ID 的数据副本
|
||||
error = None
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Commit Updates — 物理同步 (Stage 2.5)
|
||||
|
||||
> **代码**:`DataSource.sync_all()` → Handler.update()
|
||||
|
||||
```
|
||||
输入:action = UPDATE, status = PENDING
|
||||
|
||||
┌─ Handler 构建 API 请求
|
||||
│
|
||||
├─ Handler 发现无实际变更字段
|
||||
│ └──► action → NONE(Handler 降级,不提交 API)
|
||||
│
|
||||
├─ 同步 API 成功(200)
|
||||
│ └──► status → SUCCESS
|
||||
│
|
||||
├─ 异步 API 提交成功(202)
|
||||
│ └──► status → IN_PROGRESS → (轮询)→ SUCCESS / FAILED
|
||||
│
|
||||
└─ API 失败
|
||||
└──► status → FAILED
|
||||
error = "远程操作失败: ..."
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 持久化阶段 (Stage 5)
|
||||
|
||||
```
|
||||
节点当前状态原样写入存储。
|
||||
|
||||
持久化字段:
|
||||
✅ node_id, data_id, data, origin_data
|
||||
✅ binding_status, action, status, error, context
|
||||
❌ depend_ids(不持久化,下次 bind 时从 data 实时计算)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 全景时间线表
|
||||
|
||||
下表展示**一个节点在典型场景中的状态变化**:
|
||||
|
||||
### 场景 A:首次创建(本地 → 远程)
|
||||
|
||||
| 阶段 | binding_status | action | status | data_id | error |
|
||||
|------|---------------|--------|--------|---------|-------|
|
||||
| 数据加载 | UNCHECKED | NONE | PENDING | "abc-123" | None |
|
||||
| bind phase1 | **MISSING** | NONE | PENDING | "abc-123" | None |
|
||||
| bind phase2 | MISSING | NONE | PENDING | "abc-123" | None |
|
||||
| bind phase3 | MISSING | NONE | PENDING | "abc-123" | None |
|
||||
| create | **NORMAL** | NONE | PENDING | "abc-123" | None |
|
||||
| _(新建远程节点)_ | NORMAL | **CREATE** | PENDING | **""** | None |
|
||||
| commit create | NORMAL | CREATE | **SUCCESS** | **"remote-456"** | None |
|
||||
| 持久化 | NORMAL | CREATE | SUCCESS | "remote-456" | None |
|
||||
|
||||
### 场景 B:日常更新
|
||||
|
||||
| 阶段 | binding_status | action | status | data_id | error |
|
||||
|------|---------------|--------|--------|---------|-------|
|
||||
| 持久化恢复 | NORMAL→**UNCHECKED** | CREATE→保留 | SUCCESS→保留 | "abc-123" | 清空 |
|
||||
| 阶段2 action重置 | UNCHECKED | **NONE** | SUCCESS | "abc-123" | None |
|
||||
| 数据源覆盖 | UNCHECKED | NONE | SUCCESS | "abc-123" | None |
|
||||
| bind phase1 | **NORMAL** | NONE | SUCCESS | "abc-123" | None |
|
||||
| update 检测差异 | NORMAL | NONE | SUCCESS | "abc-123" | None |
|
||||
| _(目标节点)_ | NORMAL | **UPDATE** | **PENDING** | "remote-456" | None |
|
||||
| commit update | NORMAL | UPDATE | **SUCCESS** | "remote-456" | None |
|
||||
|
||||
### 场景 C:依赖阻断
|
||||
|
||||
| 阶段 | binding_status | action | status | data_id | error |
|
||||
|------|---------------|--------|--------|---------|-------|
|
||||
| 数据加载 | UNCHECKED | NONE | PENDING | "contract-789" | None |
|
||||
| bind phase1 | **MISSING** | NONE | PENDING | "contract-789" | None |
|
||||
| bind phase2 | **DEPENDENCY_ERROR** | NONE | PENDING | "contract-789" | "依赖项不满足: project_id: 状态=dep_err" |
|
||||
| _(后续阶段不处理)_ | | | | | |
|
||||
| 持久化 | DEPENDENCY_ERROR | NONE | PENDING | "contract-789" | "依赖项不满足..." |
|
||||
|
||||
### 场景 D:CREATE 失败 → 下次自动恢复
|
||||
|
||||
| 阶段 | binding_status | action | status | data_id | error |
|
||||
|------|---------------|--------|--------|---------|-------|
|
||||
| **第 1 次运行** | | | | | |
|
||||
| create | NORMAL | CREATE | PENDING | "" | None |
|
||||
| commit create 失败 | NORMAL | CREATE | **FAILED** | "" | "远程操作失败: 500" |
|
||||
| 持久化 | NORMAL | CREATE | FAILED | "" | "远程操作失败: 500" |
|
||||
| **第 2 次运行** | | | | | |
|
||||
| 阶段1恢复 | **UNCHECKED** | CREATE | FAILED | "" | **None** |
|
||||
| 阶段2僵尸清理 | _(节点被删除,绑定被解除)_ | | | | |
|
||||
| 数据源重新加载 | UNCHECKED | NONE | PENDING | "" → 新 node_id | None |
|
||||
| bind → create | 重新走首次创建流程 | | | | |
|
||||
|
||||
### 场景 E:UPDATE 失败 → 保留供人工处理
|
||||
|
||||
| 阶段 | binding_status | action | status | data_id | error |
|
||||
|------|---------------|--------|--------|---------|-------|
|
||||
| **第 1 次运行** | | | | | |
|
||||
| update | NORMAL | UPDATE | PENDING | "remote-456" | None |
|
||||
| commit update 失败 | NORMAL | UPDATE | **FAILED** | "remote-456" | "403 Forbidden" |
|
||||
| 持久化 | NORMAL | UPDATE | FAILED | "remote-456" | "403 Forbidden" |
|
||||
| **第 2 次运行** | | | | | |
|
||||
| 阶段1恢复 | UNCHECKED | UPDATE | FAILED | "remote-456" | None |
|
||||
| 阶段2 action重置 | UNCHECKED | **NONE** | **FAILED** | "remote-456" | None |
|
||||
| bind | NORMAL | NONE | FAILED | "remote-456" | None |
|
||||
| update 跳过 | _(status=FAILED,自动跳过)_ | | | | |
|
||||
| 持久化 | NORMAL | NONE | FAILED | "remote-456" | None |
|
||||
| _(需人工重置 status 后才能重新更新)_ | | | | | |
|
||||
|
||||
---
|
||||
|
||||
## 状态转换合法性约束
|
||||
|
||||
### binding_status 允许的转换
|
||||
|
||||
```
|
||||
UNCHECKED → NORMAL (核心判定: 有绑定且健康)
|
||||
UNCHECKED → MISSING (核心判定: 无绑定记录)
|
||||
UNCHECKED → ABNORMAL (核心判定: 自身数据损坏)
|
||||
UNCHECKED → WARNING (核心判定: 对方数据损坏)
|
||||
|
||||
MISSING → DEPENDENCY_ERROR (依赖检查: 父节点不满足)
|
||||
MISSING → WARNING (自动绑定: 业务键缺失 / 多候选 / auto_bind=False)
|
||||
MISSING → DEPENDENCY_ERROR (自动绑定: ID 字段解析失败)
|
||||
MISSING → NORMAL (自动绑定: 1:1 成功)
|
||||
MISSING → NORMAL (create: 源节点创建成功后)
|
||||
MISSING → DEPENDENCY_ERROR (create: ID 解析失败)
|
||||
```
|
||||
|
||||
> **注意**:当前 `SyncNode.set_binding_status()` 不做转换合法性校验【未实现】。
|
||||
|
||||
### action 允许的转换
|
||||
|
||||
```
|
||||
NONE → CREATE (create: 新建目标节点)
|
||||
NONE → UPDATE (update: 检测到差异)
|
||||
UPDATE → NONE (Handler 降级: 实际无变更字段)
|
||||
```
|
||||
|
||||
### status 允许的转换
|
||||
|
||||
```
|
||||
PENDING → IN_PROGRESS (DataSource: 异步提交成功)
|
||||
PENDING → SUCCESS (DataSource: 同步执行成功)
|
||||
PENDING → FAILED (DataSource: 执行失败)
|
||||
PENDING → SKIPPED (DataSource: 被跳过)
|
||||
(常见于 CREATE/UPDATE:Handler 返回 SKIPPED(无可执行操作/业务条件不满足),对应状态机里的 E19→S13 或 E20→S14)
|
||||
PENDING → PRECONDITION_FAILED (Strategy: 前置条件不满足)
|
||||
IN_PROGRESS → SUCCESS (DataSource: 异步轮询成功)
|
||||
IN_PROGRESS → FAILED (DataSource: 异步轮询失败/超时)
|
||||
```
|
||||
|
||||
> **注意**:当前 `SyncNode.set_status()` 不做转换合法性校验【未实现】。
|
||||
|
||||
---
|
||||
|
||||
## 参考文档
|
||||
|
||||
- [状态定义规范](./状态定义规范.md) — 状态枚举和判定规则
|
||||
- [持久化与人工介入规范](./持久化与人工介入规范.md) — 持久化重置策略
|
||||
- [编排流程规范](./编排流程规范.md) — Pipeline 阶段定义
|
||||
- [架构分层设计](./架构分层设计.md) — 各层职责
|
||||
- [node_id_data_id_lifecycle](./node_id_data_id_lifecycle.md) — ID 生命周期
|
||||
Reference in New Issue
Block a user