chore: delete useless docs
This commit is contained in:
@@ -1,131 +0,0 @@
|
||||
建议将 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模块
|
||||
@@ -1,177 +0,0 @@
|
||||
# Plan: 条件路由 (Conditional Routing / RoutingCondition)
|
||||
|
||||
## 已与用户确认的关键决策
|
||||
1. transitions 挂在 `ProcessDefinition` 级别(独立列表),不改动 `ApprovalStep`。
|
||||
2. `RoutingCondition` 采用与 `AssigneeResolver` 一致的模式:全局单例 bean,
|
||||
`boolean matches(String conditionKey, ProcessContext context)`;数据库只存 `conditionKey`
|
||||
字符串,不持久化任何 Java lambda/表达式(因为 JDBC 场景下引擎重启要从数据库重新装配
|
||||
`ProcessDefinition`,无法反序列化任意代码逻辑)。
|
||||
3. **不保留线性回退**:每个 `ProcessDefinition` 必须显式声明 transitions,覆盖每个 step 的
|
||||
出边;没有 transitions 完全是非法状态(构造期校验报错),不再依赖 `steps()` 列表顺序做
|
||||
执行期推进(`step_order`/列表顺序仅用于候选人/去重校验和展示,不再决定执行流)。
|
||||
4. 匹配不到任何 transition 时,fail-fast 抛出 `NoRouteFoundException`(不静默回退)。
|
||||
|
||||
## 模型设计 (ordo-api)
|
||||
|
||||
### 新增 `StepTransition` record
|
||||
```
|
||||
record StepTransition(String fromStepId, String toStepId, String conditionKey, int priority)
|
||||
```
|
||||
- `toStepId == null` 表示"终止流程"(当匹配到这条边时,实例被判 APPROVED 并完成)。
|
||||
- `conditionKey == null/blank` 表示无条件(总是匹配),用作 else/默认分支。
|
||||
- 便捷工厂:
|
||||
- `StepTransition.always(fromStepId, toStepId)` — 无条件跳转,priority=0
|
||||
- `StepTransition.when(fromStepId, toStepId, conditionKey, priority)` — 条件跳转
|
||||
- `StepTransition.end(fromStepId)` — 无条件终止
|
||||
- `StepTransition.endWhen(fromStepId, conditionKey, priority)` — 条件终止
|
||||
- 评估顺序:按 `priority` 升序,第一个匹配的生效(无条件 = 恒真)。
|
||||
|
||||
### 新增 `RoutingCondition` functional interface(与 AssigneeResolver 同目录/同风格)
|
||||
```
|
||||
@FunctionalInterface
|
||||
public interface RoutingCondition {
|
||||
boolean matches(String conditionKey, ProcessContext context);
|
||||
static RoutingCondition always() { return (key, context) -> true; }
|
||||
}
|
||||
```
|
||||
|
||||
### `ProcessDefinition` 改为 4 个分量(破坏性变更)
|
||||
```
|
||||
record ProcessDefinition(String id, String name, List<ApprovalStep> steps, List<StepTransition> transitions)
|
||||
```
|
||||
构造期校验(在现有 id/name/steps 非空+去重校验基础上新增):
|
||||
- `transitions` 非 null,`List.copyOf` 防御性拷贝。
|
||||
- 每条 transition 的 `fromStepId` 必须是 `steps` 中存在的 id;`toStepId`(非 null 时)也必须存在。
|
||||
- 每个 step 的 id 必须至少作为一条 transition 的 `fromStepId` 出现一次(强制显式声明出边),
|
||||
否则 `IllegalArgumentException("step X has no outgoing transition")`。
|
||||
- 同一 `(fromStepId, priority)` 不能重复(避免评估顺序有歧义)。
|
||||
- 新增便捷工厂 `ProcessDefinition.linear(id, name, steps)`:自动生成"上一步无条件指向下一步、
|
||||
最后一步无条件终止"的 transitions,等价于旧的线性行为,用于降低无需真正条件路由的测试/demo
|
||||
的迁移成本(生成的仍然是显式 transitions,满足"强制显式"的约束,只是自动拼装)。
|
||||
|
||||
### 新增异常 `NoRouteFoundException extends OrdoException`
|
||||
- message 形如 `"no matching transition for step: <stepId> in instance: <instanceId>"`。
|
||||
- 放在 `com.jetlumen.ordo.api.exception` 包,命名/风格对齐现有 `DefinitionNotFoundException` 等。
|
||||
|
||||
## 引擎改造 (ordo-core)
|
||||
|
||||
### `DefaultOrdoEngine`
|
||||
- 构造函数新增 `RoutingCondition routingCondition` 参数(在 `assigneeResolver` 之后插入,
|
||||
与 Spring autoconfigure bean 顺序一致)。
|
||||
- 删除 `indexOf(...)` 私有方法(不再需要基于下标推进)。
|
||||
- `advanceOrComplete(instance, definition, step, now)` 重写为:
|
||||
1. 调用新的私有 `resolveTransition(definition, step, context)`:
|
||||
过滤 `definition.transitions()` 中 `fromStepId == step.id()` 的项,按 `priority` 升序排序,
|
||||
依次判断 `conditionKey` 为空/blank(无条件恒真)或 `routingCondition.matches(conditionKey, context)`,
|
||||
返回第一个匹配的 `StepTransition`;若无匹配,抛出 `NoRouteFoundException`。
|
||||
2. 若匹配结果 `toStepId() == null` → `completeInstance(instance, ProcessStatus.APPROVED, now)`。
|
||||
3. 否则 → `createStepTasks(instance, requireStep(definition, matched.toStepId()), now)`。
|
||||
- `advanceAfterDecision` 中原有 ANY/ALL 策略判断逻辑不变,只是"推进到下一步"的调用点改为走上面的
|
||||
transition 解析,不再用 `stepIndex == size-1` 判断是否最后一步。
|
||||
- `NoRouteFoundException` 在 `transactionExecutor.execute(...)` 内抛出,天然触发整个 approve/reject
|
||||
事务回滚(复用现有 `rollsBackTheWholeApprovalWhenTheNextStepCannotBeCreated` 验证过的回滚机制)。
|
||||
- `start()` 的起始 step 仍取 `definition.steps().getFirst()`(未改变,属于本次范围外的假设,
|
||||
如需显式声明起始 step 可后续再提出)。
|
||||
|
||||
### `InMemoryOrdoEngine`
|
||||
- 仿照现有 `AssigneeResolver` 构造函数重载模式,新增:
|
||||
- `InMemoryOrdoEngine(RoutingCondition routingCondition)`
|
||||
- `InMemoryOrdoEngine(AssigneeResolver assigneeResolver, RoutingCondition routingCondition)`
|
||||
- `InMemoryOrdoEngine(Clock clock, AssigneeResolver assigneeResolver, RoutingCondition routingCondition)`
|
||||
- 默认无参/仅 Clock 的构造函数内部使用 `RoutingCondition.always()`。
|
||||
|
||||
## 持久化 (ordo-storage-jdbc)
|
||||
|
||||
### 新迁移 `V3__add_step_transitions.sql`
|
||||
```sql
|
||||
CREATE TABLE ordo_step_transition (
|
||||
definition_id VARCHAR(64) NOT NULL,
|
||||
from_step_id VARCHAR(64) NOT NULL,
|
||||
to_step_id VARCHAR(64),
|
||||
condition_key VARCHAR(255),
|
||||
priority INTEGER NOT NULL,
|
||||
PRIMARY KEY (definition_id, from_step_id, priority),
|
||||
CONSTRAINT fk_transition_from FOREIGN KEY (definition_id, from_step_id)
|
||||
REFERENCES ordo_approval_step (definition_id, step_id),
|
||||
CONSTRAINT fk_transition_to FOREIGN KEY (definition_id, to_step_id)
|
||||
REFERENCES ordo_approval_step (definition_id, step_id)
|
||||
);
|
||||
```
|
||||
**重要提醒(已踩过的坑,来自 /memories/repo/any-all-multi-approval.md)**:
|
||||
- 必须同步把这个新迁移文件加进 `ordo-storage-jdbc/src/test/java/.../JdbcTestSupport.java` 的
|
||||
`MIGRATIONS` 数组,否则测试库缺表导致全部 JDBC 测试失败。
|
||||
- 注释里不要出现分号(或依赖已有的按行剥离 `--` 注释逻辑,已经是健壮的,无需再担心)。
|
||||
|
||||
### 新增 `StepTransitionMapper`(仿照 `ApprovalStepMapper` 的 `StepRow` 模式)
|
||||
- `readRow(ResultSet) -> TransitionRow(fromStepId, toStepId, conditionKey, priority)`。
|
||||
|
||||
### `JdbcProcessDefinitionRepository`
|
||||
- `insertIfAbsent`:在插入 steps/candidates 之后,遍历 `definition.transitions()` 执行
|
||||
`INSERT INTO ordo_step_transition (...)`。
|
||||
- `findById`:新增 `SELECT_TRANSITIONS`(按 `from_step_id, priority` 排序),组装
|
||||
`List<StepTransition>` 传入新的 4 参 `ProcessDefinition` 构造函数。
|
||||
|
||||
## Spring Boot 自动配置 (ordo-spring-boot-autoconfigure)
|
||||
- `OrdoJdbcAutoConfiguration` 新增:
|
||||
```java
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public RoutingCondition ordoRoutingCondition() {
|
||||
return RoutingCondition.always();
|
||||
}
|
||||
```
|
||||
- `ordoEngine(...)` 方法签名新增 `RoutingCondition ordoRoutingCondition` 参数,传给
|
||||
`DefaultOrdoEngine` 构造函数。
|
||||
- `OrdoJdbcAutoConfigurationTest`:更新 `LEAVE_REQUEST` 定义使用 `ProcessDefinition.linear(...)`;
|
||||
新增一个 `honoursUserDefinedRoutingCondition` 测试,仿照现有
|
||||
`honoursUserDefinedAssigneeResolver` 的写法。
|
||||
|
||||
## 调用点迁移(约 29 处 `new ProcessDefinition(...)`)
|
||||
- 绝大多数纯线性场景(测试/demo 不需要真正的条件路由)直接把
|
||||
`new ProcessDefinition(id, name, steps)` 换成 `ProcessDefinition.linear(id, name, steps)`。
|
||||
- 受影响文件:
|
||||
- `ordo-api/src/test/java/.../ProcessDefinitionTest.java`(同时新增 transitions 相关校验用例:
|
||||
缺少出边报错、fromStepId/toStepId 引用不存在的 step 报错、重复 priority 报错)
|
||||
- `ordo-core/src/test/java/.../InMemoryOrdoEngineTest.java`(8 处调用点 + 新增条件路由专项测试)
|
||||
- `ordo-spring-boot-autoconfigure/src/test/java/.../OrdoJdbcAutoConfigurationTest.java`
|
||||
- `ordo-storage-jdbc/src/test/java/.../Jdbc*Test.java`(约 10 处,含
|
||||
`JdbcProcessDefinitionRepositoryTest` 需要新增"读写 transitions 往返"专项用例)
|
||||
- `ordo-storage-jdbc/src/main/java/.../JdbcProcessDefinitionRepository.java`(`findById` 内部
|
||||
组装 `ProcessDefinition` 那一处,改为传入真实加载到的 transitions)
|
||||
- `ordo-example/src/main/java/.../LeaveRequestExample.java`
|
||||
- `d:\Projects\Personal\java\rhizome\src\main\java\...\LeaveRequestDemoService.java`
|
||||
|
||||
## 新增测试(条件路由专项,主要在 `InMemoryOrdoEngineTest` 或新建
|
||||
`ConditionalRoutingTest`)
|
||||
1. 基础分支:manager 通过后,根据 `conditionKey`(如 `"amount-gt-1000"`)走向不同的下一 step
|
||||
(高额 -> director 审批 step;低额 -> 直接 end),验证 `RoutingCondition.matches` 被正确调用
|
||||
并影响推进结果。
|
||||
2. 无匹配 transition 时抛出 `NoRouteFoundException`,且整个 approve 事务回滚(沿用现有
|
||||
rollback 测试模式)。
|
||||
3. `ProcessDefinition` 构造期校验:
|
||||
- 某 step 没有任何出边 -> `IllegalArgumentException`。
|
||||
- transition 引用不存在的 `fromStepId`/`toStepId` -> `IllegalArgumentException`。
|
||||
- 同一 `(fromStepId, priority)` 重复 -> `IllegalArgumentException`。
|
||||
4. JDBC 往返测试:`JdbcProcessDefinitionRepositoryTest` 新增用例,插入一个带条件分支的
|
||||
definition,`findById` 读回后 `transitions()` 内容与写入一致(包括 `toStepId == null` 终止边)。
|
||||
|
||||
## 范围外 / 明确不做
|
||||
- 不支持在 transition 上直接内嵌 Java lambda(会破坏 JDBC 持久化重启场景,已由用户确认)。
|
||||
- 不引入显式 `startStepId` 概念,起始步骤仍是 `steps().getFirst()`。
|
||||
- 不做并行分支/合并(fork-join),仅支持"单一当前 step -> 单一下一 step 或终止"的有向图推进,
|
||||
与现有 ANY/ALL 多候选人机制正交(transition 发生在"某个 step 整体被判定通过/拒绝"之后)。
|
||||
|
||||
## 实施顺序(有依赖关系,需按序执行)
|
||||
1. ordo-api:`StepTransition`、`RoutingCondition`、`ProcessDefinition`(含 `linear` 工厂)、
|
||||
`NoRouteFoundException`。
|
||||
2. ordo-core:`DefaultOrdoEngine` 改造、`InMemoryOrdoEngine` 构造函数重载。
|
||||
3. ordo-storage-jdbc:`V3` 迁移 + `StepTransitionMapper` + `JdbcProcessDefinitionRepository` 改造
|
||||
+ 同步更新 `JdbcTestSupport.MIGRATIONS`。
|
||||
4. ordo-spring-boot-autoconfigure:`RoutingCondition` bean 及 `ordoEngine(...)` 装配。
|
||||
5. 迁移全部 ~29 处旧调用点(`ProcessDefinition.linear(...)`),含 rhizome demo。
|
||||
6. 新增条件路由专项测试(引擎行为 + 校验 + JDBC 往返)。
|
||||
|
||||
## 验证
|
||||
- `ordo-api`/`ordo-core`/`ordo-storage-jdbc`/`ordo-spring-boot-autoconfigure` 全量单元测试通过。
|
||||
- 手动确认 rhizome 端到端 curl 流程(线性场景切到 `ProcessDefinition.linear` 后)仍然可用
|
||||
(非阻塞,可在实现完成后单独验证)。
|
||||
Reference in New Issue
Block a user