first commit

This commit is contained in:
strepsiades
2026-03-09 16:31:42 +08:00
commit 4a9c444b10
486 changed files with 52479 additions and 0 deletions
+360
View File
@@ -0,0 +1,360 @@
# sync_state_machine 接入手册
## 1. 目标
本项目现在可以作为可编辑安装库使用:
- 源码开发:`pip install -e .`
- 代码引用:`import sync_state_machine`
- 命令行运行:`ecm-sync-run --config_path=...`
推荐接入方式:
- `sync_state_machine` 负责同步引擎、状态机、pipeline、datasource 抽象。
- 业务系统负责提供自己的数据源适配层,不要反向让 `sync_state_machine` 依赖业务系统。
---
## 2. 安装方式
### 2.1 本仓库本地安装
在仓库根目录执行:
```bash
pip install -e .
```
安装后可直接使用:
```python
import sync_state_machine
print(sync_state_machine.__version__)
```
也可以直接走命令:
```bash
ecm-sync-run --config_path=run_profiles/preset/jsonl_to_api.default.yaml
```
---
## 3. 对外可直接引用的入口
常用入口:
- `sync_state_machine.build_config`
- `sync_state_machine.build_default_config`
- `sync_state_machine.run_pipeline_from_config`
- `sync_state_machine.create_pipeline_from_config`
- `sync_state_machine.PipelineRunConfig`
- `sync_state_machine.JsonlDataSourceConfig`
- `sync_state_machine.ApiDataSourceConfig`
- `sync_state_machine.DomainRegistry`
内置 domain 注册会在导入包时自动完成。
---
## 4. 最小接入示例
### 4.1 直接运行现有 pipeline
```python
from pathlib import Path
from sync_state_machine import build_config, load_overrides_from_file, run_pipeline_from_config
async def main() -> None:
project_root = Path("/path/to/ecm_sync_system")
profile_path = project_root / "run_profiles" / "preset" / "jsonl_to_api.default.yaml"
overrides = load_overrides_from_file(profile_path, project_root)
config = build_config(project_root=project_root, overrides=overrides)
await run_pipeline_from_config(config, title="demo", print_summary=True)
```
适合场景:
- 仍沿用当前仓库的 domain 注册
- 仍沿用当前仓库的 JSONL / API handler
- 外部系统只想“调用引擎”
---
## 5. 数据源怎么引用
当前引擎的 datasource 分两层:
1. `DataSource`
- 管理加载、批量执行、轮询、collection 注入、handler 注册。
2. `Handler`
- 只处理某一个 `node_type` 的 I/O 逻辑。
因此接入时,不是“直接把业务 ORM 塞进 pipeline”,而是:
- 先确定你的数据源类型
- 再决定复用已有 datasource,还是只新增 handler
### 5.1 已有两类 datasource
#### JSONL
可直接复用:
- `sync_state_machine.datasource.JsonlDataSource`
- `sync_state_machine.datasource.BaseJsonlHandler`
适合:
- 本地 fixture
- 离线回放
- 调试数据集
#### API
可直接复用:
- `sync_state_machine.datasource.ApiDataSource`
- `sync_state_machine.datasource.ApiClient`
- `sync_state_machine.datasource.BaseApiHandler`
适合:
- HTTP 接口推送
- 异步 push + poll
- 远端系统同步
---
## 6. 怎么适配数据源
### 6.1 最推荐:复用已有 datasource,只写 handler
这是最小成本方案。
比如你的本地数据来自 backend 的数据库,但你不想改引擎核心,可以:
- 保持 `ApiDataSource` / `JsonlDataSource` 不变
- 新增你自己的 handler
- 在 handler 里把 backend 数据模型转换成节点 schema
如果是 HTTP 源:继承 `BaseApiHandler`
如果是 JSONL 源:继承 `BaseJsonlHandler`
#### API handler 示例
```python
from pydantic import BaseModel
from sync_state_machine.datasource.api.handler import BaseApiHandler
class DemoSchema(BaseModel):
id: str
name: str
class DemoApiHandler(BaseApiHandler[DemoSchema]):
_node_type = "demo"
_node_class = DemoSyncNode
_schema = DemoSchema
async def load(self):
payload = await self.api_client.get("/demo/list")
return [self._create_node(item) for item in payload["data"]]
async def create_all(self, nodes):
...
async def update_all(self, nodes):
...
async def delete_all(self, nodes):
...
async def poll_tasks(self, task_ids):
...
```
#### JSONL handler 示例
```python
from sync_state_machine.datasource.jsonl.handler import BaseJsonlHandler
class DemoJsonlHandler(BaseJsonlHandler):
def __init__(self, datasource):
super().__init__(
datasource=datasource,
node_type="demo",
node_class=DemoSyncNode,
schema=DemoSchema,
)
```
然后把它注册到 `DomainRegistry`
---
### 6.2 如果 backend 是数据库:建议写“数据库 handler / adapter”,不要让引擎直接依赖 ORM
推荐结构:
```text
backend/
app/
integrations/
ecm_sync/
datasource/
user_db_handler.py
contract_db_handler.py
mapper/
user_mapper.py
contract_mapper.py
services/
sync_runner.py
```
职责建议:
- `mapper/`
- backend model -> sync schema
- sync schema -> backend push payload
- `datasource/handler`
- 调 repository / service 获取数据
- 转成 `SyncNode`
- `services/sync_runner.py`
- 组装配置并调用 pipeline
不要这样做:
- 不要在 `sync_state_machine` 包里 import backend 的 model/repository
- 不要让 `DomainRegistry` 注册逻辑散落在 backend 业务层之外
---
### 6.3 什么时候需要新增 DataSource 类
只有当你的 I/O 模型与当前两种 datasource 都不匹配时,才新增 `BaseDataSource` 子类。
例如:
- MQ / Kafka 批量消费型源
- 直接数据库快照源
- 特殊 RPC 源
此时建议:
1. 继承 `BaseDataSource`
2. 保持接口与现有 datasource 一致:
- `register_handler()`
- `set_collection()`
- `load_all()`
- `sync_all()`
3. 把“节点类型差异”仍下沉到 handler,不要写死在 datasource 里
---
## 7. Domain 怎么注册
引擎通过 `DomainRegistry` 查找:
- `schema`
- `node_class`
- `strategy_class`
- `jsonl_handler_class`
- `api_handler_class`
示例:
```python
from sync_state_machine.common.registry import DomainRegistry
DomainRegistry.register(
node_type="demo",
schema=DemoSchema,
node_class=DemoSyncNode,
strategy_class=DemoStrategy,
jsonl_handler_class=DemoJsonlHandler,
api_handler_class=DemoApiHandler,
)
```
如果是 backend 接入,建议在 backend 的 integration 启动阶段集中注册,而不是分散在 controller / service 里。
---
## 8. backend 里的推荐接法
建议 backend 只新增一层 integration
```python
from pathlib import Path
from sync_state_machine import build_config, create_pipeline_from_config
async def run_demo_sync() -> None:
project_root = Path("/path/to/ecm_sync_system")
overrides = {
"node_types": ["project", "contract"],
"local_datasource": {
"type": "jsonl",
"jsonl_dir": str(project_root / "tests" / "fixtures" / "demo"),
},
"remote_datasource": {
"type": "api",
"api_base_url": "https://example.com",
"api_uid": "xxx",
"api_secret": "xxx",
"target_project_ids": ["demo-project"],
},
}
config = build_config(project_root=project_root, overrides=overrides)
pipeline = await create_pipeline_from_config(config)
await pipeline.run()
```
如果 backend 自己提供本地数据库数据,不要把 backend model 直接灌进 pipeline;先经过 handler / mapper 转成 schema 再进入引擎。
---
## 9. 接入时的边界约束
建议保持下面的依赖方向:
- `backend integration -> sync_state_machine`
- `sync_state_machine -X-> backend`
这样做的好处:
- 引擎能独立测试
- backend 改 repository 不会直接污染引擎
- 后续可以单独发布库
---
## 10. 当前阶段建议
如果项目还在高频迭代,推荐顺序:
1.`pip install -e .`
2. 先在 backend 做 integration 目录,不急着发私有包
3. 先接 1~2 个 node_type,跑通完整链路
4. 稳定后再考虑拆成正式发布包
---
## 11. 相关文件
- 包入口:[sync_state_machine/__init__.py](../sync_state_machine/__init__.py)
- CLI[sync_state_machine/cli.py](../sync_state_machine/cli.py)
- 配置工厂:[sync_state_machine/config](../sync_state_machine/config)
- datasource 基础层:[sync_state_machine/datasource](../sync_state_machine/datasource)
- pipeline 工厂:[sync_state_machine/pipeline/factory.py](../sync_state_machine/pipeline/factory.py)
- domain 注册:[sync_state_machine/common/registry.py](../sync_state_machine/common/registry.py)