Appearance
规范 05 · SPI 接口
接口签名契约。接口为什么这样划界的论证见 设计原理 05 · SPI 设计; 扩展仓储(管理扩展)的设计动机见 设计原理 07 · 管理扩展与门面。
IProcessRepository(必须实现)
引擎与存储层的唯一接口。各语言遵循自身命名约定(Java/Node=camelCase,Go=PascalCase),语义一致。
text
┌──────────────────────────────────────────────┐
│ <<interface>> │
│ IProcessRepository │
├──────────────────────────────────────────────┤
│ + findDefineById(id): ProcessDefine │
│ + saveDefine(def): void (v1.0.1) │
│ + updateDefine(def): void (v1.0.1) │
│ + updateDefineState(id, state): void (v1.0.1)│
│ + removeDefine(id): void (v1.0.1) │
│ + findInstanceById(id): ProcessInstance │
│ + saveInstance(inst): void │
│ + updateInstance(inst): void │
│ + findTaskById(taskId): ProcessTask │
│ + saveTask(task): void │
│ + updateTask(task): void │
│ + findDoingTasks(instanceId, taskNames): [] │
│ + findDoneTasks(instanceId, taskNames): [] │
│ + findHistoryTasks(instanceId): [] │
│ + findTaskActors(taskId): [] │
│ + addTaskActor(taskId, actors): void │
│ + removeTaskActor(taskId, actors): void │
│ + createCcInstance(instId, creator, ...ids) │
│ + updateCcStatus(instId, actorId): void │
│ + pageCcInstances(query, actorId): Page │
└──────────────────────────────────────────────┘updateInstance 级联(v1.0.1,集成反馈):
updateInstance(inst)级联持久化聚合根内 任务状态变更——撤回/挂起/废弃等聚合命令改完任务状态后,随updateInstance同一连接落库, 集成方无需再逐个updateTask。前提:聚合根内任务副本反映最新状态 (引擎在完成任务后会同步聚合内任务副本)。
各语言实现对照:
| 操作 | Java | Go | Node.js | Python |
|---|---|---|---|---|
| 查流程定义 | findDefineById(Long) | FindDefineByID(int64) | findDefineById(id: number) | find_define_by_id(id) |
| 保存定义 | saveDefine(ProcessDefine) | SaveDefine(*ProcessDefine) | saveDefine(def) | save_define(def) |
| 更新定义 | updateDefine(ProcessDefine) | UpdateDefine(*ProcessDefine) | updateDefine(def) | update_define(def) |
| 定义启停 | updateDefineState(Long, int) | UpdateDefineState(int64, int) | updateDefineState(id, state) | update_define_state(id, state) |
| 删除定义 | removeDefine(Long) | RemoveDefine(int64) | removeDefine(id) | remove_define(id) |
| 查实例 | findInstanceById(Long) | FindInstanceByID(int64) | findInstanceById(id: number) | find_instance_by_id(id) |
| 保存实例 | saveInstance(ProcessInstance) | SaveInstance(*ProcessInstance) | saveInstance(inst) | save_instance(inst) |
| 更新实例 | updateInstance(ProcessInstance) | UpdateInstance(*ProcessInstance) | updateInstance(inst) | update_instance(inst) |
| 查任务 | findTaskById(Long) | FindTaskByID(int64) | findTaskById(id: number) | find_task_by_id(id) |
| 保存任务 | saveTask(ProcessTask) | SaveTask(*ProcessTask) | saveTask(task) | save_task(task) |
| 更新任务 | updateTask(ProcessTask) | UpdateTask(*ProcessTask) | updateTask(task) | update_task(task) |
| 进行中任务 | findDoingTasks(Long, String[]) | FindDoingTasks(int64, []string) | findDoingTasks(id, taskNames?) | find_doing_tasks(id, task_names) |
| 已完成任务 | findDoneTasks(Long, String[]) | FindDoneTasks(int64, []string) | findDoneTasks(id, taskNames?) | find_done_tasks(id, task_names) |
| 历史任务 | findHistoryTasks(Long) | FindHistoryTasks(int64) | findHistoryTasks(id) | find_history_tasks(id) |
| 查参与者 | findTaskActors(Long) | FindTaskActors(int64) | findTaskActors(id) | find_task_actors(id) |
| 添加参与者 | addTaskActor(Long, List) | AddTaskActor(int64, []string) | addTaskActor(id, actors) | add_task_actor(id, actors) |
| 移除参与者 | removeTaskActor(Long, List) | RemoveTaskActor(int64, []string) | removeTaskActor(id, actors) | remove_task_actor(id, actors) |
| 抄送创建 | createCcInstance(Long, String, String...) | CreateCcInstance(int64, string, ...string) | createCcInstance(id, creator, ...ids) | create_cc_instance(id, creator, *ids) |
| 抄送状态 | updateCcStatus(Long, String) | UpdateCcStatus(int64, string) | updateCcStatus(id, actorId) | update_cc_status(id, actor_id) |
| 抄送分页(v1.3.0) | pageCcInstances(PageQuery, String) | PageCcInstances(ctx, PageQuery, string) | pageCcInstances(pageNum, pageSize, actorId) | page_cc_instances(page_num, page_size, actor_id) |
addTaskActor语义(v1.3.0 明确):追加——查已有参与者、去重后仅插入新增, 不清空原参与者(对齐 boot2/boot3;转交/加签/委托后原处理人保留可办)。 JDBC 参考实现 v1.2.0 曾为覆盖语义(先删后插),v1.3.0 修复; 全量重置场景用removeTaskActor+addTaskActor组合。
IUserProvider
text
┌────────────────────────┐
│ <<interface>> │
│ IUserProvider │
├────────────────────────┤
│ + getUser(userId): UserInfo │
└────────────────────────┘
UserInfo
├── userId: String
├── realName: String
├── deptId: String
├── deptName: String
├── postId: String
└── postName: String可选 SPI
| 接口 | 方法 | 说明 |
|---|---|---|
IJsonProvider | toJson(obj) / fromJson(str) | JSON 序列化 |
IExpressionEvaluator | eval(expr, vars) | 决策表达式求值 |
ITransactionTemplate | execute(action) | 事务管理 |
IIdGenerator | nextId() | ID 生成 |
事务约定
原则:
- 引擎核心不感知事务——引擎方法只调仓储接口,不知道事务存在(引擎方法内部多次仓储调用是否同事务,由调用方/集成层决定)
- 事务由业务层(调用方)持有——业务代码是事务的唯一 owner:开启 → 业务操作 + 引擎调用 → commit / rollback
- 事务是连接级的——同事务内所有仓储方法必须使用同一数据库连接
- 接口契约不变——仓储接口签名不携带事务参数,事务通过各语言的上下文绑定机制传递
上下文绑定机制(四语言):
| 语言 | 机制 | 说明 |
|---|---|---|
| Java | ThreadLocal(Spring 事务管理器) | @Transactional 方法内仓储自动获得事务连接;非 Spring 用户用 ITransactionTemplate |
| Go | context.Context | 引擎方法第一个参数为 ctx(Go 生态惯例),Tx 存入 ctx,仓储从 ctx 取出 |
| Python | contextvars.ContextVar | async with repo.transaction(): 内部把连接写入 ContextVar,引擎/业务 DAO 自动取用 |
| Node | AsyncLocalStorage(node:async_hooks) | await repo.withTransaction(async () => {...}) 内部 ALS.run() 绑定连接 |
业务层形态:
java
// Java(Spring)
@Transactional
public void approve(Long taskId, String operator) {
orderService.updateStatus(orderId, "APPROVED"); // 业务表(同事务连接)
engine.executeProcessTask(taskId, operator, args); // 仓储同事务
}go
// Go(ctx 携带 Tx)
ctx := context.WithValue(r.Context(), TxKey, tx)
err := repo.WithTx(ctx, func(ctx context.Context) error {
orderSvc.UpdateStatus(ctx, orderID, "APPROVED")
_, e := engine.ExecuteProcessTask(ctx, taskID, op, args)
return e
})python
# Python(async 上下文管理器)
async with repo.transaction():
await order_svc.update_status(order_id, "APPROVED")
await engine.execute_process_task(task_id, op, args)ts
// Node(withTransaction 包装)
await repo.withTransaction(async () => {
await orderSvc.updateStatus(orderId, 'APPROVED')
await engine.executeProcessTask(taskId, op, args)
})仓储实现要求:
- 仓储方法优先使用上下文绑定的连接(同一事务内的多次调用走同一连接);无绑定连接时回退为独立连接(单操作场景)
- 事务包装(
WithTx/transaction()/withTransaction)由仓储提供——它持有连接池,事务时 checkout 一个连接并绑定到上下文,业务回调结束后 commit/rollback 并释放 - 业务 DAO 必须与流程仓储同源取连接(同样走上下文绑定),否则业务操作与流程操作不在同一事务
- 内存仓储为 no-op——
transaction()直接执行回调(内存操作天然原子),接口形态与其他语言一致,切换真实仓储时业务代码零改动
约束:
- 跨数据库/分布式事务(2PC/Saga)不在本规范范围——工作流引擎按单库事务设计,跨服务一致性由业务层自管
- 引擎方法不应在内部自行开启事务(事务边界应由业务层控制,引擎只负责语义)
扩展仓储(IProcessExtRepository,可选)
引擎核心不依赖本节内容。设计稿 / 历史 / 委托是"周边管理能力",通过扩展仓储 SPI 提供统一读写,集成方可选接入;
SurrogateInterceptor为委托生效的参考实现。
text
┌──────────────────────────────────────────────┐
│ <<interface>> (可选 SPI) │
│ IProcessExtRepository │
├──────────────────────────────────────────────┤
│ # 流程设计(wf_process_design) │
│ + findDesignById(id): ProcessDesign │
│ + saveDesign(design): void │
│ + updateDesign(design): void │
│ + removeDesign(id): void │
│ + pageDesigns(query): Page<DesignRow> │
│ # 设计历史(wf_process_design_his) │
│ + saveDesignHis(his): void │
│ + listDesignHis(designId): []DesignHis │
│ # 委托代理(wf_process_surrogate) │
│ + findSurrogateById(id): ProcessSurrogate │
│ + saveSurrogate(s): void │
│ + updateSurrogate(s): void │
│ + removeSurrogate(id): void │
│ + pageSurrogates(query): Page<SurrogateRow> │
│ + getSurrogate(operator, processName, time) │
│ : ProcessSurrogate|null # 生效中的委托 │
└──────────────────────────────────────────────┘- 分页查询参数/返回约定与
IProcessRepository的pageDefines等一致(表别名t) getSurrogate生效规则:enabled=1且start_time <= time <= end_time(时间窗为空表示不限), 优先匹配process_name精确命中;process_name为空(全流程委托)作为兜底- 各语言实现位置:Java
spi/IProcessExtRepository.java+JdbcProcessExtRepository; Gospi/spi.go(ProcessExtRepository)+repository/jdbc;Pythonspi.py+repository/base.py; Nodespi.ts+src/jdbc/shared.ts;均配内存实现供测试
SurrogateInterceptor(委托生效,参考实现)
任务创建后(PROCESS_TASK_START 前),遍历任务参与者:
- 对每个 actor 调用
getSurrogate(actor, processName, now)(processName = 流程定义 name) - 命中委托 → 把代理人加入任务参与者(
addTaskActor),原授权人保留(任一可办)
参考实现放在各语言 demo / starter,默认不注册——集成方按需挂载到 FlowInterceptor 列表。 委托在 todoList 的合并展示属于集成方视图层职责(参考 boot3:
ProcessTaskServiceImpl.todoList)。
参考实现(多数据库适配架构)
各语言仓库内置,可直接用于生产;另附内存实现供测试。
每语言一个共享核心(SQL 逻辑唯一维护点,占位符统一 ?)+ 每库一个薄适配器 (连接 + 占位符风格),新增数据库 ≈ 适配器 80 行 + 建表 SQL:
| 语言 | 共享核心 | 数据库适配 | 驱动安装 |
|---|---|---|---|
| Java | jeeflow-repository-jdbc → JdbcProcessRepository | javax.sql.DataSource(任意 JDBC 驱动) | Maven 依赖 |
| Go | repository/jdbc → jdbc.New(db) | database/sql 任意驱动(换驱动+DSN) | go get 驱动 |
| Python | jeeflow/repository/base.py → JdbcRepository(adapter) | mysql.py(aiomysql)/ postgres.py(asyncpg) | pip install jeeflow[mysql] / jeeflow[postgres] |
| Node.js | src/jdbc/shared.ts → JdbcRepository(adapter) | mysql.ts(mysql2)/ postgres.ts(pg) | npm i mysql2 / pg(optionalDependencies) |
事务绑定:Java = Spring
@Transactional/ITransactionTemplate;Go =WithTx(ctx, fn); Python =with_tx(fn)(ContextVar);Node =withTx(fn)(AsyncLocalStorage)。 建表 SQL 各语言自带(使用者单语言下载即用):tests/schema/schema-<db>.sql(Go 为repository/jdbc/schema/)。维护约定:编辑源在 jeeflow-java 仓jeeflow-repository-jdbc/src/test/resources/(schema-h2/mysql/postgres 并排), 改表结构后跑jeeflow-hub/scripts/sync-schema.sh分发到各语言。