Your in-flight workflows called and they do not want breaking changes.
This repository contains the demo code for a talk on Dapr Workflow versioning. It shows what goes wrong when you deploy a breaking workflow change while older workflow instances are still running ("in-flight"), and how to evolve a workflow safely using Dapr's two versioning techniques:
IsPatched— for small, additive changes within an existing workflow.- Named versioning — for large, breaking changes via a new workflow type (e.g.
DiagnosticsWorkflowV2).
When V2 of a workflow is deployed, V1 instances may still be running. The workflow engine will try to resume them against state that was written by V1 — but the V2 code expects different state, and replays will fail.
graph LR
A[App with Workflow V2]
B[Workflow Engine]
C[(State Store<br/>V1 state)]
A <--> B
B --> C
C --> B
The fix is to keep V1's behavior reachable for in-flight instances while running V2 for new ones.
A diagnostics workflow for the starship USS Enterprise. It analyzes ship subsystems, generates recommendations, and notifies the bridge. The workflow is deliberately evolved across three versions to exercise both versioning techniques.
flowchart LR
Start([Start]) --> Hull[Analyze Hull]
Hull --> Warp[Analyze Warp Core]
Warp --> Sec[Analyze Security]
Sec --> Rec[Generate Recommendations]
Rec --> Notify[Notify Bridge]
Notify --> End([End])
A weapon-systems analysis is added. Existing in-flight instances skip the new branch via context.IsPatched("AddWeaponsAnalysis"); new instances run the full path.
A new workflow type, DiagnosticsWorkflowV2, runs all four analyses in parallel. V1 stays registered so in-flight V1 instances keep completing.
flowchart TD
Start([Start V2]) --> Fan{Fan-out}
Fan --> Hull[Analyze Hull]
Fan --> Warp[Analyze Warp Core]
Fan --> Sec[Analyze Security]
Fan --> Weap[Analyze Weapons]
Hull --> Join{Fan-in}
Warp --> Join
Sec --> Join
Weap --> Join
Join --> Rec[Generate Recommendations]
Rec --> Notify[Notify Bridge]
Notify --> End([End])
DiagnosticsWorkflowV3 adds a WorkflowRetryPolicy (5 attempts, 3s initial, 1.5x backoff) to every activity call.
Two solutions ship side by side. The Aspire one is the demo starting point that evolves through the versions live; the no-Aspire one is the finished reference.
graph LR
Service[ApiService]
Sidecar[Dapr Sidecar<br/>Workflow Engine]
Store[(Valkey<br/>State Store)]
Service <--> Sidecar
Sidecar --> Store
Store --> Sidecar
- .NET 10 + Aspire CLI orchestrates the ApiService, Dapr sidecar, Valkey state store, and the Diagrid Dev Dashboard
- Conversation API (Anthropic) component is available; activities use mocked outputs by default
- Ships with V0 active and V2/V3 as
.cs.tempfiles so the talk can introduce each version step by step
graph LR
Service[ApiService]
Sidecar[Dapr Sidecar<br/>Workflow Engine]
Store[(Local State Store)]
Service <--> Sidecar
Sidecar --> Store
Store --> Sidecar
- Same workflow code, but no Aspire — run with
dapr run -f .using the includeddapr.yaml - Contains the final state of all three workflow versions (
DiagnosticsWorkflow,DiagnosticsWorkflowV2,DiagnosticsWorkflowV3) registered side by side - Use this solution as a reference for the completed implementation, not to follow the demo evolution
Prerequisites:
- .NET 10 SDK
- Aspire CLI
- Docker Desktop
- Dapr CLI 1.17+ (for the local Dapr solution)
- Diagrid CLI (for the Catalyst solution)
Run the local Dapr version:
cd EnterpriseDiagnostics
aspire runTrigger workflows via the .http file in EnterpriseDiagnostics.ApiService or:
curl -X POST http://localhost:5467/start \
-H "Content-Type: application/json" \
-d '{"id":"diag-001","shipName":"USS Enterprise NCC-1701-D","diagnosticsDate":"2370-04-08","engineerName":"Geordi La Forge"}'Each solution's own README has the full API surface and run details.
- Always version your workflows once you have a system running in production.
- Use
IsPatchedfor small, additive changes. - Use named versioning for large or breaking changes.