Quick Start
Build a complete approval workflow in 5 minutes.
The Scenario
We'll create a workflow that:
- Validates an incoming request
- Waits for approval via an external signal
- Routes to different outcomes based on the decision
Complete Example
dart
import 'package:vyuh_workflow_engine/vyuh_workflow_engine.dart';
Future<void> main() async {
// 1. Create deserialization context
final context = RegistryDeserializationContext(
descriptors: [DefaultWorkflowDescriptor()],
);
// 2. Create storage (in-memory for this example)
final storage = InMemoryStorage(context: context);
// 3. Initialize the engine
final engine = WorkflowEngine(
context: context,
storage: storage,
);
await engine.initialize();
// 3. Define the approval workflow
final workflow = WorkflowBuilder('APPROVAL', 'Simple Approval Workflow')
// Entry point
.start('begin')
// Validate the request
.task('validate', name: 'Validate Request', execute: (ctx) async {
// Use getInitial<T> for original workflow input
final entityId = ctx.getInitial<String>('entityId');
if (entityId == null) {
throw ArgumentError('entityId is required');
}
return {'validated': true, 'timestamp': DateTime.now().toIso8601String()};
})
// Wait for approval decision
.signalWait(
'awaitApproval',
name: 'Await Approval',
signal: 'approval_decision',
storeAs: 'approvalResult',
)
// Route based on decision
.oneOf('routeDecision', [
Branch.whenFn(
(output) => output['approvalResult']?['decision'] == 'approved',
then: 'handleApproved',
label: 'Approved',
),
Branch.whenFn(
(output) => output['approvalResult']?['decision'] == 'rejected',
then: 'handleRejected',
label: 'Rejected',
),
Branch.otherwise(then: 'handleRejected'),
])
// Handle approved
.task('handleApproved', name: 'Process Approval', execute: (ctx) async {
// Use getAny<T> with dot-notation for accumulated output
final approvedBy = ctx.getAny<String>('approvalResult.approvedBy');
print('Request approved by $approvedBy');
return {'finalStatus': 'approved', 'processedAt': DateTime.now().toIso8601String()};
})
// Handle rejected
.task('handleRejected', name: 'Process Rejection', execute: (ctx) async {
print('Request rejected');
return {'finalStatus': 'rejected'};
})
// Terminal nodes
.end('completed', name: 'Completed')
.end('rejected', name: 'Rejected')
// Define the flow
.connect('begin', 'validate')
.connect('validate', 'awaitApproval')
.connect('awaitApproval', 'routeDecision')
.connect('handleApproved', 'completed')
.connect('handleRejected', 'rejected')
.build();
// 4. Register the workflow
engine.registerWorkflow(workflow);
// 5. Start a workflow instance
final instance = await engine.startWorkflow(
workflowCode: workflow.code, // Use workflowCode for semantic lookup
input: {
'entityId': 'REQ-001',
'entityType': 'PurchaseOrder',
'submittedBy': '[email protected]',
},
);
print('Workflow started: ${instance.id}');
print('Status: ${instance.status}'); // waitingForSignal
// 6. Simulate approval (in real app, this comes from UI)
await engine.sendSignal(
workflowInstanceId: instance.id,
node: 'awaitApproval', // The node ID waiting for the signal
payload: {
'decision': 'approved',
'approvedBy': '[email protected]',
'comments': 'Looks good!',
},
);
// 7. Check final status
final completed = await storage.instances.getById(instance.id);
print('Final status: ${completed?.status}'); // completed
print('Output: ${completed?.output}');
}Understanding the Flow
Key Concepts Introduced
| Concept | Description |
|---|---|
WorkflowBuilder | Fluent API for defining workflows |
start() | Entry point for the workflow |
task() | Automated work execution |
signalWait() | Pause and wait for external signal |
oneOf() | Exclusive gateway (XOR) for routing |
Branch | Define routing conditions |
end() | Terminal point for the workflow |
connect() | Connect nodes in the flow |
Next Steps
- Core Philosophy - Understand the design principles
- Architecture - Deep dive into the engine
- Node Types - Learn about all node types