Workflow patterns
Common durable workflow patterns supported by Catalyst Workflows (built on Dapr Workflow):
Task Chaining

Multiple steps run in succession, with the output of one step passed as input to the next. Ideal for sequential data processing operations like filtering, transforming, and reducing.
Read more →Fan-out / Fan-in

Execute multiple tasks simultaneously across potentially multiple workers, wait for them to finish, and aggregate the results. Perfect for parallel processing with dynamic task counts.
Read more →Async HTTP APIs

Return an immediate HTTP 202, then expose a status endpoint the caller can poll. No custom state management required for long-running operations.
Read more →Monitor

A recurring loop that wakes on a durable timer, checks an external condition, takes action, and repeats. Supports dynamic sleep intervals and graceful termination.
Read more →External System Interaction

Pause and wait for external systems or human input. Supports timeouts, event-driven resumption, and compensation logic for approval workflows.
Read more →Compensation

Roll back or undo operations that have already executed when a workflow fails partway through. Essential for consistency in distributed transactions.
Read more →Task chaining
Execute a sequence of activities in order, passing output from one step to the next. If any step fails, the workflow retries it from that step — earlier steps are not re-executed.
Fan-out / Fan-in
Spawn multiple activity instances in parallel, then aggregate their results once all complete. Catalyst Workflows tracks each branch independently and resumes the aggregation step only after all branches succeed.
Async HTTP (polling consumer)
Start a long-running task, return an HTTP 202 to the caller immediately, then expose a status endpoint the caller can poll. The workflow runs durably in the background regardless of how long the task takes.
Monitor
A loop that wakes periodically (using a durable timer), checks an external condition, and sends a notification or raises an event when the condition is met. The timer survives process restarts.
External events / Human-in-the-loop
Pause execution at a step that waits for an external event — an approval, a webhook callback, or a manual signal. The workflow remains durable while waiting; you can raise the event from the CLI or the console.
The example below shows a request-escalation workflow: orders under $1,000 are auto-approved; larger orders wait for a human approval event with a 7-day timeout.
- Python
- JavaScript
- .NET
- Java
- Go
@wfr.workflow(name="request_escalation")
def request_escalation_workflow(ctx: DaprWorkflowContext, order_str: str):
order = json.loads(order_str) if isinstance(order_str, str) else order_str
amount = order.get("amount", 0)
# Auto-approve orders under $1000
if amount < 1000:
yield ctx.call_activity(auto_approve_activity, input=order)
return
# Orders $1000+ require human approval (7 days max)
approval_event = ctx.wait_for_external_event("human_approval")
timeout = ctx.create_timer(timedelta(days=7))
winner = yield when_any([approval_event, timeout])
if winner == timeout:
yield ctx.call_activity(handle_timeout_activity, input=order)
return
approval_result = approval_event.get_result()
yield ctx.call_activity(process_approval_activity, input=approval_result)
const requestEscalationWorkflow: TWorkflow = async function* (
ctx: WorkflowContext,
order: OrderRequest
): any {
// Auto-approve orders under $1000
if (order.amount < 1000) {
yield ctx.callActivity(autoApproveActivity, order);
return;
}
// Orders $1000+ require human approval (7 days max)
const approvalEvent = ctx.waitForExternalEvent("human_approval");
const timeout = ctx.createTimer(7 * 24 * 60 * 60);
const winner = yield ctx.whenAny([approvalEvent, timeout]);
if (winner == timeout) {
yield ctx.callActivity(handleTimeoutActivity, order);
return;
}
const approvalResult = approvalEvent.getResult();
yield ctx.callActivity(processApprovalActivity, approvalResult);
};
public class RequestEscalationWorkflow : Workflow<OrderRequest, object>
{
public override async Task<object> RunAsync(WorkflowContext context, OrderRequest order)
{
// Auto-approve orders under $1000
if (order.Amount < 1000)
{
await context.CallActivityAsync(nameof(AutoApproveActivity), order);
return null;
}
// Orders $1000+ require human approval (7 days max)
try
{
var approval = await context.WaitForExternalEventAsync<ApprovalResult>(
eventName: "human_approval",
timeout: TimeSpan.FromDays(7));
await context.CallActivityAsync(nameof(ProcessApprovalActivity), approval);
return null;
}
catch (TaskCanceledException)
{
await context.CallActivityAsync(nameof(HandleTimeoutActivity), order);
return null;
}
}
}
@Component
public static class RequestEscalationWorkflow implements Workflow {
@Override
public WorkflowStub create() {
return ctx -> {
OrderRequest order = ctx.getInput(OrderRequest.class);
// Auto-approve orders under $1000
if (order.amount < 1000) {
ctx.callActivity("AutoApproveActivity", order).await();
ctx.complete("approved");
return;
}
// Orders $1000+ require human approval (7 days max)
try {
ApprovalResult approval = ctx.waitForExternalEvent(
"human_approval", Duration.ofDays(7), ApprovalResult.class).await();
ctx.callActivity("ProcessApprovalActivity", approval).await();
} catch (TaskCanceledException e) {
ctx.callActivity("HandleTimeoutActivity", order).await();
}
ctx.complete("order completed");
};
}
}
func RequestEscalationWorkflow(ctx *workflow.WorkflowContext) (any, error) {
var order OrderRequest
if err := ctx.GetInput(&order); err != nil {
return nil, err
}
// Auto-approve orders under $1000
if order.Amount < 1000 {
return nil, ctx.CallActivity(AutoApproveActivity, workflow.WithActivityInput(order)).Await(nil)
}
// Orders $1000+ require human approval (7 days max)
var approval ApprovalResult
err := ctx.WaitForExternalEvent("human_approval", time.Hour*24*7).Await(&approval)
if err == nil {
return nil, ctx.CallActivity(ProcessApprovalActivity, workflow.WithActivityInput(approval)).Await(nil)
}
return nil, ctx.CallActivity(HandleTimeoutActivity, workflow.WithActivityInput(order)).Await(nil)
}
Raise the event from the CLI once the approval is ready:
diagrid workflow raise-event --app-id my-workflow-app \
--instance-id <id> --event-name human_approval --event-data '{"approved": true}'