Skip to content

规范 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。前提:聚合根内任务副本反映最新状态 (引擎在完成任务后会同步聚合内任务副本)。

各语言实现对照

操作JavaGoNode.jsPython
查流程定义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

接口方法说明
IJsonProvidertoJson(obj) / fromJson(str)JSON 序列化
IExpressionEvaluatoreval(expr, vars)决策表达式求值
ITransactionTemplateexecute(action)事务管理
IIdGeneratornextId()ID 生成

事务约定

原则

  1. 引擎核心不感知事务——引擎方法只调仓储接口,不知道事务存在(引擎方法内部多次仓储调用是否同事务,由调用方/集成层决定)
  2. 事务由业务层(调用方)持有——业务代码是事务的唯一 owner:开启 → 业务操作 + 引擎调用 → commit / rollback
  3. 事务是连接级的——同事务内所有仓储方法必须使用同一数据库连接
  4. 接口契约不变——仓储接口签名不携带事务参数,事务通过各语言的上下文绑定机制传递

上下文绑定机制(四语言)

语言机制说明
JavaThreadLocal(Spring 事务管理器)@Transactional 方法内仓储自动获得事务连接;非 Spring 用户用 ITransactionTemplate
Gocontext.Context引擎方法第一个参数为 ctx(Go 生态惯例),Tx 存入 ctx,仓储从 ctx 取出
Pythoncontextvars.ContextVarasync with repo.transaction(): 内部把连接写入 ContextVar,引擎/业务 DAO 自动取用
NodeAsyncLocalStorage(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)
})

仓储实现要求

  1. 仓储方法优先使用上下文绑定的连接(同一事务内的多次调用走同一连接);无绑定连接时回退为独立连接(单操作场景)
  2. 事务包装(WithTx / transaction() / withTransaction)由仓储提供——它持有连接池,事务时 checkout 一个连接并绑定到上下文,业务回调结束后 commit/rollback 并释放
  3. 业务 DAO 必须与流程仓储同源取连接(同样走上下文绑定),否则业务操作与流程操作不在同一事务
  4. 内存仓储为 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   # 生效中的委托 │
└──────────────────────────────────────────────┘
  • 分页查询参数/返回约定与 IProcessRepositorypageDefines 等一致(表别名 t
  • getSurrogate 生效规则:enabled=1start_time <= time <= end_time(时间窗为空表示不限), 优先匹配 process_name 精确命中;process_name 为空(全流程委托)作为兜底
  • 各语言实现位置:Java spi/IProcessExtRepository.java + JdbcProcessExtRepository; Go spi/spi.go(ProcessExtRepository)+ repository/jdbc;Python spi.py + repository/base.py; Node spi.ts + src/jdbc/shared.ts;均配内存实现供测试

SurrogateInterceptor(委托生效,参考实现)

任务创建后(PROCESS_TASK_START 前),遍历任务参与者:

  1. 对每个 actor 调用 getSurrogate(actor, processName, now)(processName = 流程定义 name)
  2. 命中委托 → 把代理人加入任务参与者(addTaskActor),原授权人保留(任一可办)

参考实现放在各语言 demo / starter,默认不注册——集成方按需挂载到 FlowInterceptor 列表。 委托在 todoList 的合并展示属于集成方视图层职责(参考 boot3:ProcessTaskServiceImpl.todoList)。

参考实现(多数据库适配架构)

各语言仓库内置,可直接用于生产;另附内存实现供测试。

每语言一个共享核心(SQL 逻辑唯一维护点,占位符统一 ?)+ 每库一个薄适配器 (连接 + 占位符风格),新增数据库 ≈ 适配器 80 行 + 建表 SQL:

语言共享核心数据库适配驱动安装
Javajeeflow-repository-jdbcJdbcProcessRepositoryjavax.sql.DataSource(任意 JDBC 驱动)Maven 依赖
Gorepository/jdbcjdbc.New(db)database/sql 任意驱动(换驱动+DSN)go get 驱动
Pythonjeeflow/repository/base.pyJdbcRepository(adapter)mysql.py(aiomysql)/ postgres.py(asyncpg)pip install jeeflow[mysql] / jeeflow[postgres]
Node.jssrc/jdbc/shared.tsJdbcRepository(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 分发到各语言。

jeeflow · 轻量级多语言工作流引擎