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>
This commit is contained in:
0264408
2026-09-08 16:56:04 +08:00
co-authored by Claude Code
commit 397a9c229b
58 changed files with 2779 additions and 0 deletions
+131
View File
@@ -0,0 +1,131 @@
建议将 JDBC 做成独立模块,并在实现前先补上“跨仓储事务”和“并发安全”两个能力;否则一次审批会拆成多次独立数据库操作,容易留下半完成流程。
1. 新建模块
```text
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。
2. 先补事务边界
在 `ordo-api` 增加一个通用端口:
```java
public interface TransactionExecutor {
<T> T execute(Supplier<T> action);
}
```
`DefaultOrdoEngine` 的 `start`、`approve`、`reject` 应在同一个事务中执行。
这保证:
- 发起流程时,“创建实例 + 创建第一条任务”要么都成功,要么都回滚。
- 审批时,“完成旧任务 + 创建下一任务 / 结束实例”要么都成功,要么都回滚。
内存实现提供无操作事务执行器;JDBC 实现使用同一条线程绑定的 `Connection`,执行 `commit` 或 `rollback`。
3. 调整仓储 SPI 的并发语义
当前 `save` 是覆盖式写入,JDBC 下无法避免两个用户同时审批同一任务。建议在落 JDBC 前调整:
```java
boolean insertIfAbsent(ProcessDefinition definition);
boolean completeIfPending(ApprovalTask completedTask);
```
`completeIfPending` 对应 SQL:
```sql
UPDATE ordo_approval_task
SET status = ?, completed_at = ?, action_actor = ?, action_comment = ?, action_at = ?
WHERE id = ? AND status = 'PENDING'
```
受影响行数为 `0` 时,抛出 `TaskAlreadyCompletedException`。这比仅依赖 JVM 内的 `synchronized` 更可靠。
4. 数据库模型
采用规范化表,不把步骤和任务都塞进 JSON。
```text
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
```
至少建立:
```text
ordo_approval_task(instance_id)
ordo_approval_task(status, assignee)
ordo_process_instance(definition_id)
```
5. `ProcessContext` 的持久化
`ProcessContext.variables` 适合存为 `context_json`。在 JDBC 模块内部使用 Jackson 做序列化与反序列化,不要让 `ordo-api` 依赖 Jackson。
v0.1 可以约定上下文仅支持 JSON 兼容值:字符串、数字、布尔值、列表、嵌套 Map。日期、枚举和自定义 Java 对象以后再通过可插拔 `ContextCodec` 解决。
6. JDBC 实现顺序
- 先写 `V1__create_ordo_tables.sql`
- 实现 `JdbcTransactionExecutor`
- 实现定义仓储与步骤读写
- 实现实例仓储及 `context_json`
- 实现任务仓储及待办查询
- 调整 `DefaultOrdoEngine` 使用事务和条件更新
- 为 JDBC 仓储添加集成测试
7. 测试策略
先用 H2 快速验证 CRUD 和映射;并发条件更新、时间类型、唯一约束等最终应使用 Testcontainers 加 PostgreSQL 或 MySQL 验证。
建议第一版目标是 PostgreSQL;表结构、`timestamp` 语义和 JSON 支持都会更明确。等 JDBC 实现稳定后,再创建 Spring Boot Starter:Starter 只负责注入 `DataSource`、JDBC 仓储、事务执行器和 `DefaultOrdoEngine`。
帮忙按照这个实现一下JDBC模块