Gateways
Gateways control workflow routing, enabling conditional branching and parallel execution.
Gateway Types
| Type | Builder | Description |
|---|---|---|
| Exclusive | .oneOf() | Route to exactly ONE path (XOR) |
| Inclusive | .anyOf() | Route to ONE OR MORE paths (OR) |
| Parallel | .allOf() | Route to ALL paths (AND) |
Exclusive Gateway (oneOf)
Routes to exactly ONE path based on the first matching condition.
dart
builder.oneOf('routeDecision', [
Branch.whenFn(
(output) => output['amount'] > 10000,
then: 'requireManagerApproval',
label: 'High Value',
),
Branch.whenFn(
(output) => output['amount'] > 1000,
then: 'requireSupervisorApproval',
label: 'Medium Value',
),
Branch.otherwise(then: 'autoApprove'),
]);Behavior
- Evaluates conditions in priority order (highest first)
- Takes the first matching path
- Falls back to default branch if no match
Custom Condition Executors
dart
class HighValueCondition extends ConditionExecutor {
const HighValueCondition() : super(schemaType: 'condition.highValue');
@override
Future<bool> execute(ExecutionContext context) async {
// Use get<T> for previous node output
return (context.get<num>('amount') ?? 0) > 10000;
}
@override
Map<String, dynamic> toJson() => {'schemaType': schemaType};
static final typeDescriptor = TypeDescriptor<ConditionExecutor>(
schemaType: 'condition.highValue',
fromJson: (_) => const HighValueCondition(),
);
}
// Register via descriptor
final descriptor = WorkflowDescriptor(
title: 'Custom Conditions',
conditions: [HighValueCondition.typeDescriptor],
);
// Use expression-based conditions in builder (simpler)
builder.oneOf('routeByValue', [
Branch.when("amount > 10000", then: 'highValuePath'),
Branch.otherwise(then: 'normalPath'),
]);Inclusive Gateway (anyOf)
Race multiple paths - first to complete wins.
dart
builder.anyOf('notifyRace', [
'emailNotification',
'smsNotification',
'pushNotification',
]);Behavior
- Starts ALL paths concurrently
- First path to complete wins
- Other paths are cancelled
WARNING
Use anyOf for race conditions where you want the fastest path to win.
Parallel Gateway (allOf)
Routes to ALL paths unconditionally, enabling concurrent execution.
Fork (Split)
dart
builder.allOf('parallelStart', [
'validateInventory',
'processPayment',
'sendConfirmation',
]);Join (Synchronize)
The same gateway type is used for joining parallel branches:
dart
// Fork
builder.allOf('startParallel', ['taskA', 'taskB', 'taskC']);
// Join - all branches must arrive before continuing
builder.allOf('joinParallel', ['afterJoin']);
// Edges
builder
.connect('startParallel', 'taskA')
.connect('startParallel', 'taskB')
.connect('startParallel', 'taskC')
.connect('taskA', 'joinParallel')
.connect('taskB', 'joinParallel')
.connect('taskC', 'joinParallel')
.connect('joinParallel', 'afterJoin');Visual
Join Behavior
- Join waits for ALL incoming branches
- Each arriving token is deactivated
- When all arrive, a new token is created
- Execution continues past the join
Branch Definition
dart
class Branch {
// Expression-based condition
static Branch when(String expression, {required String then, String? label});
// Function-based routing (inline)
static Branch whenFn(ConditionEvaluator evaluator, {required String then, String? label});
// Equality check
static Branch whenEquals(String variable, Object value, {required String then});
// Boolean check
static Branch whenTrue(String variable, {required String then});
// Null check
static Branch whenNotNull(String variable, {required String then});
// Default/fallback
static Branch otherwise({required String then});
}Priority
Conditions are evaluated in the order they appear in the list:
dart
builder.oneOf('decide', [
Branch.whenFn((o) => o['urgent'] == true, then: 'urgentPath'), // Evaluated first
Branch.whenFn((o) => o['important'] == true, then: 'importantPath'), // Evaluated second
Branch.otherwise(then: 'normalPath'), // Fallback
]);The first matching condition wins - order your branches from most specific to least specific.
Common Patterns
Approval Routing
dart
builder.oneOf('routeApproval', [
Branch.whenEquals('decision', 'approved', then: 'handleApproved'),
Branch.whenEquals('decision', 'rejected', then: 'handleRejected'),
Branch.whenEquals('decision', 'revision', then: 'requestRevision'),
Branch.otherwise(then: 'handleRejected'),
]);Loop Control
dart
builder.oneOf('checkContinue', [
Branch.whenTrue('hasMore', then: 'loopBody'),
Branch.otherwise(then: 'exitLoop'),
]);Error Handling
dart
builder.oneOf('checkResult', [
Branch.whenTrue('success', then: 'continueProcess'),
Branch.when("error.retryable == true", then: 'retryTask'),
Branch.otherwise(then: 'handleError'),
]);Best Practices
- Always include default branch - Catch unexpected cases
- Use descriptive labels - Document the routing logic
- Keep conditions simple - Complex logic in executor classes
- Test all paths - Ensure coverage of all branches
- Name gateways clearly -
routeByAmountnotgateway1
Next Steps
- Control Flow Patterns - Gateway patterns
- Loop Patterns - Iteration with gateways
- Condition Executors - Custom conditions