Workflows¶
Workflows in Floxide orchestrate the execution of nodes. A workflow is a directed graph of nodes, where the edges represent transitions between nodes. This page explains how to create, configure, and execute workflows.
The Workflow Struct¶
The core of workflow orchestration in Floxide is the Workflow
struct. This struct manages the execution flow between nodes, handles errors and retries, and provides observability and monitoring.
pub struct Workflow<C, A> {
// Internal implementation details
}
Where:
C
is the context type that the workflow operates onA
is the action type that the nodes in the workflow return
Creating Workflows¶
Basic Workflow¶
The simplest way to create a workflow is to use the new
method, which takes a single node:
use floxide_core::{lifecycle_node, LifecycleNode, Workflow, DefaultAction};
use std::sync::Arc;
// Create a node
let node = Arc::new(create_processor_node());
// Create a workflow with the node
let mut workflow = Workflow::new(node);
Linear Workflow¶
You can create a linear workflow by chaining nodes using the then
method:
let node1 = Arc::new(create_first_node());
let node2 = Arc::new(create_second_node());
let node3 = Arc::new(create_third_node());
let mut workflow = Workflow::new(node1)
.then(node2)
.then(node3);
This creates a workflow where node1
is executed first, followed by node2
, and then node3
.
Conditional Branching¶
You can create conditional branches in your workflow using the on
method, which takes an action and a node:
enum CustomAction {
Success,
Error,
Retry,
}
let decision_node = Arc::new(create_decision_node());
let success_node = Arc::new(create_success_node());
let error_node = Arc::new(create_error_node());
let retry_node = Arc::new(create_retry_node());
let mut workflow = Workflow::new(decision_node)
.on(CustomAction::Success, success_node)
.on(CustomAction::Error, error_node)
.on(CustomAction::Retry, retry_node);
This creates a workflow where the execution path depends on the action returned by decision_node
.
Executing Workflows¶
To execute a workflow, use the execute
method, which takes a mutable reference to a context:
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a context
let mut context = MessageContext {
input: "Hello, Floxide!".to_string(),
result: None,
};
// Create a workflow
let node = Arc::new(create_processor_node());
let mut workflow = Workflow::new(node);
// Execute the workflow
workflow.execute(&mut context).await?;
// Print the result
println!("Result: {:?}", context.result);
Ok(())
}
Advanced Workflow Patterns¶
Error Handling¶
Floxide provides several ways to handle errors in workflows:
Try-Catch Pattern¶
You can implement a try-catch pattern using conditional branching:
enum CustomAction {
Success,
Error,
}
let try_node = Arc::new(create_try_node());
let success_node = Arc::new(create_success_node());
let catch_node = Arc::new(create_catch_node());
let mut workflow = Workflow::new(try_node)
.on(CustomAction::Success, success_node)
.on(CustomAction::Error, catch_node);
Retry Pattern¶
You can implement a retry pattern by returning to a previous node:
enum CustomAction {
Success,
Retry,
Error,
}
let operation_node = Arc::new(create_operation_node());
let success_node = Arc::new(create_success_node());
let retry_node = Arc::new(create_retry_node());
let error_node = Arc::new(create_error_node());
let mut workflow = Workflow::new(operation_node)
.on(CustomAction::Success, success_node)
.on(CustomAction::Retry, retry_node)
.on(CustomAction::Error, error_node);
// Configure retry_node to return to operation_node
Parallel Execution¶
For parallel execution, you can use the BatchNode
from the floxide-batch
crate:
use floxide_batch::{batch_node, BatchNode, BatchContext};
// Create a batch node that processes items in parallel
let batch_node = Arc::new(batch_node(
10, // Concurrency limit
|item: String| async move {
Ok(item.to_uppercase())
}
));
// Create a workflow with the batch node
let mut workflow = Workflow::new(batch_node);
Event-Driven Workflows¶
For event-driven workflows, you can use the EventNode
from the floxide-event
crate:
use floxide_event::{event_node, EventNode, EventContext};
// Create an event node that responds to events
let event_node = Arc::new(event_node(
|event: String| async move {
Ok(format!("Processed event: {}", event))
}
));
// Create a workflow with the event node
let mut workflow = Workflow::new(event_node);
Workflow Composition¶
You can compose workflows by treating a workflow as a node in a larger workflow:
// Create a sub-workflow
let sub_workflow_node = Arc::new(create_sub_workflow_node());
let sub_workflow = Workflow::new(sub_workflow_node);
// Create a main workflow that includes the sub-workflow
let main_workflow_node = Arc::new(create_main_workflow_node());
let mut main_workflow = Workflow::new(main_workflow_node)
.then(Arc::new(sub_workflow));
Workflow Persistence¶
Floxide supports workflow persistence through serialization and deserialization:
use serde::{Serialize, Deserialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
struct PersistableContext {
input: String,
result: Option<String>,
state: WorkflowState,
}
// Serialize the context to save the workflow state
let serialized = serde_json::to_string(&context)?;
// Later, deserialize the context to resume the workflow
let mut context: PersistableContext = serde_json::from_str(&serialized)?;
workflow.execute(&mut context).await?;
Best Practices¶
When creating workflows, consider the following best practices:
- Keep workflows focused: Each workflow should have a clear purpose.
- Use appropriate branching: Choose the right branching pattern for your use case.
- Handle errors gracefully: Implement proper error handling and recovery mechanisms.
- Consider performance: Be mindful of performance implications, especially for parallel execution.
- Leverage type safety: Use Rust's type system to ensure type safety between nodes.
Next Steps¶
Now that you understand workflows, you can learn about: