Files
ordo/docs/jdbc-plan.md
T
0264408andClaude Code 397a9c229b feat: initial approval workflow engine with in-memory and JDBC storage
Lightweight linear approval engine (v0.1):

- ordo-api: domain model, OrdoEngine port, repository SPI with
  conditional updates (insertIfAbsent, completeIfPending) and a
  TransactionExecutor port for atomic multi-step writes
- ordo-core: DefaultOrdoEngine running register/start/approve/reject
  inside a transaction boundary; in-memory engine and repositories
- ordo-storage-jdbc: thread-bound JDBC transactions, normalized V1
  schema migration, Jackson-based ProcessContext JSON codec
- tests: unit tests plus H2 integration tests; PostgreSQL integration
  tests run against a local instance via ordo.test.pg.* properties and
  skip when unreachable

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-08 16:56:04 +08:00

4.5 KiB
Raw Blame History

建议将 JDBC 做成独立模块,并在实现前先补上“跨仓储事务”和“并发安全”两个能力;否则一次审批会拆成多次独立数据库操作,容易留下半完成流程。

  1. 新建模块
ordo-storage-jdbc/
├── pom.xml
└── src/main/
    ├── java/com/jetlumen/ordo/storage/jdbc/
    │   ├── JdbcTransactionExecutor.java
    │   ├── JdbcProcessDefinitionRepository.java
    │   ├── JdbcProcessInstanceRepository.java
    │   ├── JdbcApprovalTaskRepository.java
    │   ├── JdbcConnectionProvider.java
    │   └── mapper/
    └── resources/db/migration/
        └── V1__create_ordo_tables.sql

依赖只需要 ordo-api、javax.sql.DataSource 和 JDBC 驱动;先不要依赖 Spring。

  1. 先补事务边界

在 ordo-api 增加一个通用端口:

public interface TransactionExecutor {
    <T> T execute(Supplier<T> action);
}

DefaultOrdoEngine 的 start、approve、reject 应在同一个事务中执行。

这保证:

  • 发起流程时,“创建实例 + 创建第一条任务”要么都成功,要么都回滚。
  • 审批时,“完成旧任务 + 创建下一任务 / 结束实例”要么都成功,要么都回滚。

内存实现提供无操作事务执行器;JDBC 实现使用同一条线程绑定的 Connection,执行 commit 或 rollback。

  1. 调整仓储 SPI 的并发语义

当前 save 是覆盖式写入,JDBC 下无法避免两个用户同时审批同一任务。建议在落 JDBC 前调整:

boolean insertIfAbsent(ProcessDefinition definition);

boolean completeIfPending(ApprovalTask completedTask);

completeIfPending 对应 SQL:

UPDATE ordo_approval_task
SET status = ?, completed_at = ?, action_actor = ?, action_comment = ?, action_at = ?
WHERE id = ? AND status = 'PENDING'

受影响行数为 0 时,抛出 TaskAlreadyCompletedException。这比仅依赖 JVM 内的 synchronized 更可靠。

  1. 数据库模型

采用规范化表,不把步骤和任务都塞进 JSON。

ordo_process_definition
- id                 varchar(64) primary key
- name               varchar(255) not null

ordo_approval_step
- definition_id      varchar(64) not null
- step_id            varchar(64) not null
- step_name          varchar(255) not null
- assignee           varchar(255) not null
- step_order         integer not null
- primary key (definition_id, step_id)

ordo_process_instance
- id                 varchar(36) primary key
- definition_id      varchar(64) not null
- initiator          varchar(255) not null
- status             varchar(32) not null
- context_json       text not null
- started_at         timestamp not null
- finished_at        timestamp null

ordo_approval_task
- id                 varchar(36) primary key
- instance_id        varchar(36) not null
- step_id            varchar(64) not null
- task_name          varchar(255) not null
- assignee           varchar(255) not null
- status             varchar(32) not null
- created_at         timestamp not null
- completed_at       timestamp null
- action_actor       varchar(255) null
- action_comment     text null
- action_at          timestamp null

至少建立:

ordo_approval_task(instance_id)
ordo_approval_task(status, assignee)
ordo_process_instance(definition_id)
  1. ProcessContext 的持久化

ProcessContext.variables 适合存为 context_json。在 JDBC 模块内部使用 Jackson 做序列化与反序列化,不要让 ordo-api 依赖 Jackson。

v0.1 可以约定上下文仅支持 JSON 兼容值:字符串、数字、布尔值、列表、嵌套 Map。日期、枚举和自定义 Java 对象以后再通过可插拔 ContextCodec 解决。

  1. JDBC 实现顺序
  • 先写 V1__create_ordo_tables.sql
  • 实现 JdbcTransactionExecutor
  • 实现定义仓储与步骤读写
  • 实现实例仓储及 context_json
  • 实现任务仓储及待办查询
  • 调整 DefaultOrdoEngine 使用事务和条件更新
  • 为 JDBC 仓储添加集成测试
  1. 测试策略

先用 H2 快速验证 CRUD 和映射;并发条件更新、时间类型、唯一约束等最终应使用 Testcontainers 加 PostgreSQL 或 MySQL 验证。

建议第一版目标是 PostgreSQL;表结构、timestamp 语义和 JSON 支持都会更明确。等 JDBC 实现稳定后,再创建 Spring Boot Starter:Starter 只负责注入 DataSource、JDBC 仓储、事务执行器和 DefaultOrdoEngine。

帮忙按照这个实现一下JDBC模块