chore: delete useless docs

This commit is contained in:
0264408
2026-09-17 10:30:52 +08:00
parent 0288a8b3dd
commit e177e9592a
2 changed files with 0 additions and 308 deletions
-131
View File
@@ -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模块
-177
View File
@@ -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` 后)仍然可用
(非阻塞,可在实现完成后单独验证)。