You understand workflow architecture from the previous lessons: durable execution through event sourcing, replay-based recovery, and the determinism rules that make it all work. Now it's time to write actual workflow code.
Your Task API needs to process tasks through multiple stages: validation, assignment, and notification. Each stage might fail. The process might take hours if it requires human approval. Your pod might restart mid-processing. With traditional code, you'd lose state. With a Dapr workflow, you pick up exactly where you left off.
The dapr-ext-workflow Python SDK gives you the building blocks: workflow functions that orchestrate, activity functions that execute units of work, and a runtime that manages durability. By the end of this lesson, you'll have a working workflow that survives restarts and chains activities together seamlessly.
Start by adding the workflow extension to your project:
Output:
This installs dapr-ext-workflow and its dependency durabletask, which provides the underlying durable execution framework.
Activities are where real work happens. They call APIs, access databases, generate timestamps, read environment variables. All the non-deterministic operations that workflows cannot do directly.
An activity is a simple function decorated with @wfr.activity:
Key points about activities:
Let's add two more activities to complete our task processing pipeline:
Output when activities run:
Notice that activities print output, use datetime.utcnow(), and could call external services. This is exactly what workflows CANNOT do directly. Activities are the escape hatch for real-world operations.
A workflow function orchestrates activities. It defines the sequence, handles branching logic, and manages data flow. The magic happens with yield: each yield ctx.call_activity(...) is a durability checkpoint.
The yield keyword is not just Python's generator mechanism. In Dapr workflows, each yield is a durability checkpoint:
Each yield creates a checkpoint. If the workflow restarts, the engine replays from history: completed activities return cached results instantly, incomplete activities re-execute.
Activities and workflows must be registered with a WorkflowRuntime before they can execute. Here's a complete FastAPI application with proper lifecycle management:
Output on startup:
Output on shutdown:
The workflow runtime needs to:
Using FastAPI's lifespan context manager ensures the runtime starts before your app accepts requests and shuts down gracefully when the app terminates.
The DaprWorkflowClient schedules new workflow instances and queries their status:
Test it with curl:
Output:
Workflow logs:
Add an endpoint to check workflow progress:
Here's the full application combining everything:
Run with Dapr:
You extended your dapr-deployment skill with actor and workflow patterns in Module 7.0. Does it now cover workflow authoring?
If your skill generates proper @wfr.activity and @wfr.workflow decorators, correct yield statements, and lifespan integration, it is working correctly.
Prompt 2: Design a Multi-Activity Workflow
Prompt 3: Debug a Workflow Issue
Safety Note: When authoring workflows, remember that workflow code must be deterministic. Never use datetime.now(), random, or direct API calls inside the workflow function itself. Always put non-deterministic operations inside activities.