2c09c61165
将target_project_ids替换为更通用的data_id_filter
372 lines
9.0 KiB
Markdown
372 lines
9.0 KiB
Markdown
# sync_state_machine 接入手册
|
||
|
||
## 1. 目标
|
||
|
||
本项目现在可以作为可编辑安装库使用:
|
||
|
||
- 源码开发:`pip install -e .`
|
||
- 代码引用:`import sync_state_machine`
|
||
- 命令行运行:`ecm-sync-run --config_path=...`
|
||
|
||
推荐接入方式:
|
||
|
||
- `sync_state_machine` 负责同步引擎、状态机、pipeline、datasource 抽象。
|
||
- 业务系统负责提供自己的 datasource / handler 适配层,不要反向让 `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.create_pipeline_from_config`
|
||
- `sync_state_machine.build_config_from_file`
|
||
- `sync_state_machine.PipelineRunConfig`
|
||
- `sync_state_machine.JsonlDataSourceConfig`
|
||
- `sync_state_machine.ApiDataSourceConfig`
|
||
- `sync_state_machine.DomainRegistry`
|
||
|
||
配置加载分两步:
|
||
|
||
1. 先构造或读取 `PipelineRunConfig`
|
||
2. 再交给 `create_pipeline_from_config()` 创建 pipeline
|
||
|
||
内置 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 业务层之外
|
||
- 不要在 datasource 之外做额外的 project 过滤;项目范围应通过 `handler_configs.project.data_id_filter` 下沉到具体 handler 解释
|
||
|
||
---
|
||
|
||
### 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`
|
||
- 以及任意通过 `register_handler()` 追加的 datasource handler
|
||
|
||
示例:
|
||
|
||
```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,
|
||
)
|
||
|
||
DomainRegistry.register_handler("demo", "custom_remote", DemoCustomHandler)
|
||
```
|
||
|
||
如果是 backend 接入,建议在启动入口集中注册,而不是分散在 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",
|
||
"handler_configs": {
|
||
"project": {"data_id_filter": ["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)
|
||
- backend 集成入口:[backend/app/thirdparty/ecm_sync/services/sync_runner.py](../backend/app/thirdparty/ecm_sync/services/sync_runner.py) |