feat: evaluate built-in routing predicates and parameterized host conditions

Store transition when as JSON instead of a lookup key so designers can compose closed-set logic while hosts still supply complex refs with args. Match conditional edges before the unconditional default.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
0264408
2026-09-18 11:09:50 +08:00
co-authored by Cursor
parent e177e9592a
commit 1f6e33c14b
27 changed files with 781 additions and 83 deletions
@@ -45,7 +45,7 @@ public final class JdbcProcessDefinitionRepository implements ProcessDefinitionR
+ " VALUES (?, ?, ?, ?, ?)";
private static final String INSERT_TRANSITION =
"INSERT INTO ordo_step_transition (definition_id, definition_version, from_step_id, to_step_id,"
+ " condition_key, priority) VALUES (?, ?, ?, ?, ?, ?)";
+ " condition_json, priority) VALUES (?, ?, ?, ?, ?, ?)";
private static final String SELECT_DEFINITION =
"SELECT name FROM ordo_process_definition WHERE id = ? AND version = ?";
private static final String SELECT_STEPS =
@@ -56,7 +56,7 @@ public final class JdbcProcessDefinitionRepository implements ProcessDefinitionR
"SELECT step_id, candidate FROM ordo_step_candidate"
+ " WHERE definition_id = ? AND definition_version = ? ORDER BY step_id, candidate_order";
private static final String SELECT_TRANSITIONS =
"SELECT from_step_id, to_step_id, condition_key, priority FROM ordo_step_transition "
"SELECT from_step_id, to_step_id, condition_json, priority FROM ordo_step_transition "
+ "WHERE definition_id = ? AND definition_version = ? ORDER BY from_step_id, priority";
private static final String SELECT_LATEST_PAGE_BASE =
"SELECT p.id, p.current_version, d.name FROM ordo_process p"
@@ -260,7 +260,7 @@ public final class JdbcProcessDefinitionRepository implements ProcessDefinitionR
try (ResultSet resultSet = selectTransitions.executeQuery()) {
while (resultSet.next()) {
TransitionRow row = StepTransitionMapper.readRow(resultSet);
transitions.add(new StepTransition(row.fromStepId(), row.toStepId(), row.conditionKey(),
transitions.add(new StepTransition(row.fromStepId(), row.toStepId(), row.when(),
row.priority()));
}
}
@@ -377,7 +377,7 @@ public final class JdbcProcessDefinitionRepository implements ProcessDefinitionR
insertTransition.setInt(2, definition.version());
insertTransition.setString(3, transition.fromStepId());
insertTransition.setString(4, transition.toStepId());
insertTransition.setString(5, transition.conditionKey());
insertTransition.setString(5, transition.when() == null ? null : transition.when().toJson());
insertTransition.setInt(6, transition.priority());
insertTransition.executeUpdate();
}
@@ -1,5 +1,7 @@
package com.jetlumen.ordo.storage.jdbc.mapper;
import com.jetlumen.ordo.api.RoutingWhen;
import java.sql.ResultSet;
import java.sql.SQLException;
@@ -12,10 +14,10 @@ public final class StepTransitionMapper {
return new TransitionRow(
resultSet.getString("from_step_id"),
resultSet.getString("to_step_id"),
resultSet.getString("condition_key"),
RoutingWhen.parseJson(resultSet.getString("condition_json")),
resultSet.getInt("priority"));
}
public record TransitionRow(String fromStepId, String toStepId, String conditionKey, int priority) {
public record TransitionRow(String fromStepId, String toStepId, RoutingWhen when, int priority) {
}
}
@@ -0,0 +1,4 @@
-- Replace opaque condition_key with the full when JSON object.
ALTER TABLE ordo_step_transition ADD COLUMN condition_json TEXT;
ALTER TABLE ordo_step_transition DROP COLUMN condition_key;
@@ -0,0 +1,4 @@
-- Replace opaque condition_key with the full when JSON object.
ALTER TABLE ordo_step_transition ADD COLUMN condition_json TEXT;
ALTER TABLE ordo_step_transition DROP COLUMN condition_key;
@@ -76,7 +76,7 @@ class JdbcMysqlIntegrationTest {
}
MysqlDataSource schemaDataSource = newDataSource(DATABASE);
JdbcTestSupport.applySchema(schemaDataSource, JdbcTestSupport.MYSQL_BASELINE);
JdbcTestSupport.applySchema(schemaDataSource, JdbcTestSupport.MYSQL_BASELINE, JdbcTestSupport.MYSQL_V2);
dataSource = schemaDataSource;
}
@@ -183,7 +183,7 @@ class JdbcOrdoEngineIntegrationTest {
@Test
void rollsBackTheWholeApprovalWhenNoRouteMatches() {
OrdoEngine failingEngine = new DefaultOrdoEngine(Clock.fixed(NOW, ZoneOffset.UTC),
AssigneeResolver.direct(), (key, context) -> false, ActionHandler.noop(),
AssigneeResolver.direct(), (key, args, context) -> false, ActionHandler.noop(),
new JdbcTransactionExecutor(connectionProvider),
new JdbcProcessDefinitionRepository(connectionProvider),
new JdbcProcessInstanceRepository(connectionProvider),
@@ -193,7 +193,7 @@ class JdbcOrdoEngineIntegrationTest {
List.of());
failingEngine.publish(new ProcessDefinition("leave-noroute", "Leave request",
List.of(ApprovalStep.single("manager", "Manager approval", "maria")),
List.of(StepTransition.endWhen("manager", "never", 0))));
List.of(StepTransition.endRef("manager", "never", java.util.Map.of(), 0))));
ProcessInstance instance = failingEngine.start("leave-noroute", "alice");
ApprovalTask managerTask = failingEngine.findPendingTasksByInstanceId(instance.id()).get(0);
@@ -5,6 +5,7 @@ import com.jetlumen.ordo.api.ApprovalStep;
import com.jetlumen.ordo.api.ProcessDefinition;
import com.jetlumen.ordo.api.StepDue;
import com.jetlumen.ordo.api.StepKind;
import com.jetlumen.ordo.api.RoutingPredicate;
import com.jetlumen.ordo.api.StepTransition;
import com.jetlumen.ordo.api.query.Page;
import com.jetlumen.ordo.api.query.PageRequest;
@@ -73,7 +74,7 @@ class JdbcProcessDefinitionRepositoryTest {
ApprovalStep.single("manager", "Manager approval", "maria"),
ApprovalStep.single("director", "Director approval", "diana")),
List.of(
StepTransition.when("manager", "director", "amount-gt-1000", 0),
StepTransition.when("manager", "director", RoutingPredicate.gt("amount", 1000), 0),
new StepTransition("manager", null, null, 1),
StepTransition.end("director")));
@@ -14,7 +14,9 @@ import java.util.UUID;
/** Creates isolated in-memory H2 databases with the Ordo schema applied. */
final class JdbcTestSupport {
static final String POSTGRES_BASELINE = "/db/postgresql/migration/V1__baseline.sql";
static final String POSTGRES_V2 = "/db/postgresql/migration/V2__transition_condition_json.sql";
static final String MYSQL_BASELINE = "/db/mysql/migration/V1__baseline.sql";
static final String MYSQL_V2 = "/db/mysql/migration/V2__transition_condition_json.sql";
private JdbcTestSupport() {
}
@@ -29,15 +31,17 @@ final class JdbcTestSupport {
}
static void applySchema(DataSource dataSource) {
applySchema(dataSource, POSTGRES_BASELINE);
applySchema(dataSource, POSTGRES_BASELINE, POSTGRES_V2);
}
static void applySchema(DataSource dataSource, String resourcePath) {
String migration = loadSchema(resourcePath);
static void applySchema(DataSource dataSource, String... resourcePaths) {
try (Connection connection = dataSource.getConnection(); Statement statement = connection.createStatement()) {
for (String sql : stripComments(migration).split(";")) {
if (!sql.isBlank()) {
statement.execute(sql);
for (String resourcePath : resourcePaths) {
String migration = loadSchema(resourcePath);
for (String sql : stripComments(migration).split(";")) {
if (!sql.isBlank()) {
statement.execute(sql);
}
}
}
} catch (SQLException e) {