Skip to content

Commit c004fa0

Browse files
authored
Add Workflow Versioning Sample (#96)
* bump cadence-go-client up to 1.2.10-rc.16 * bump cadence-go-client up to 1.2.10-rc.16 * add versioning to samples * change some structure
1 parent 39a3f74 commit c004fa0

File tree

8 files changed

+467
-8
lines changed

8 files changed

+467
-8
lines changed

Makefile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export PATH := $(GOPATH)/bin:$(PATH)
77
default: test
88

99
PROGS = helloworld \
10+
versioning \
1011
delaystart \
1112
branch \
1213
childworkflow \
@@ -172,7 +173,11 @@ crossdomain-run: crossdomain
172173
sideeffect:
173174
go build -o bin/sideeffect cmd/samples/recipes/sideeffect/*.go
174175

176+
versioning:
177+
go build -o bin/versioning cmd/samples/recipes/versioning/*.go
178+
175179
bins: helloworld \
180+
versioning \
176181
delaystart \
177182
branch \
178183
crossdomain \

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,9 @@ See more details in https://github.com/uber-common/cadence-samples/blob/master/c
161161
#### recovery
162162
See more details in https://github.com/uber-common/cadence-samples/blob/master/cmd/samples/recovery/README.md
163163

164+
#### versioning
165+
See more details in https://github.com/uber-common/cadence-samples/blob/master/cmd/samples/versioning/README.md
166+
164167
## License
165168

166169
Apache 2.0 License, please see [LICENSE](https://github.com/cadence-workflow/cadence-samples/blob/master/LICENSE) for details.

cmd/samples/common/sample_helper.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ func (h *SampleHelper) RegisterActivityWithAlias(activity interface{}, alias str
245245
}
246246

247247
// StartWorkers starts workflow worker and activity worker based on configured options.
248-
func (h *SampleHelper) StartWorkers(domainName string, groupName string, options worker.Options) {
248+
func (h *SampleHelper) StartWorkers(domainName string, groupName string, options worker.Options) worker.Worker {
249249
worker := worker.New(h.Service, domainName, groupName, options)
250250
h.registerWorkflowAndActivity(worker)
251251

@@ -254,6 +254,8 @@ func (h *SampleHelper) StartWorkers(domainName string, groupName string, options
254254
h.Logger.Error("Failed to start workers.", zap.Error(err))
255255
panic("Failed to start workers")
256256
}
257+
258+
return worker
257259
}
258260

259261
func (h *SampleHelper) QueryWorkflow(workflowID, runID, queryType string, args ...interface{}) {
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
# Versioning Workflow Example
2+
3+
This example demonstrates how to safely deploy versioned workflows using Cadence's versioning APIs. It shows how to handle workflow evolution while maintaining backward compatibility and enabling safe rollbacks.
4+
5+
## Overview
6+
7+
The versioning sample implements a workflow that evolves through multiple versions (V1 → V2 → V3 → V4) with rollbacks, demonstrating:
8+
9+
- **Safe Deployment**: How to deploy new workflow versions without breaking existing executions
10+
- **Backward Compatibility**: How to handle workflows started with older versions
11+
- **Rollback Capability**: How to safely rollback to previous versions
12+
- **Version Isolation**: How different versions can execute different logic paths
13+
14+
## Workflow Versions
15+
16+
### Version 1 (V1)
17+
- Executes `FooActivity` only
18+
- Uses `workflow.DefaultVersion` for the change ID
19+
20+
### Version 2 (V2)
21+
- Supports both `FooActivity` and `BarActivity`
22+
- Uses `workflow.GetVersion()` with `workflow.ExecuteWithMinVersion()` to handle both old and new workflows
23+
- Workflows started by V1 continue using `FooActivity`
24+
25+
### Version 3 (V3)
26+
- Similar to V2 but uses standard `workflow.GetVersion()` (without `ExecuteWithMinVersion`)
27+
- All new workflows use version 1 of the change ID
28+
29+
### Version 4 (V4)
30+
- Only supports `BarActivity`
31+
- Forces all workflows to use version 1 of the change ID
32+
- **Breaking change**: Cannot execute workflows started by V1
33+
34+
## Key Cadence APIs Used
35+
36+
- `workflow.GetVersion()`: Determines which version of code to execute
37+
- `workflow.ExecuteWithVersion()`: Executes code with a specific version
38+
- `workflow.ExecuteWithMinVersion()`: Executes code with minimum version requirement
39+
- `workflow.DefaultVersion`: Represents the original version before any changes
40+
41+
## Safe Deployment Flow
42+
43+
This example demonstrates a safe deployment strategy that allows you to:
44+
45+
1. **Deploy new versions** while keeping old workers running
46+
2. **Test compatibility** before fully switching over
47+
3. **Rollback safely** if issues are discovered
48+
4. **Gradually migrate** workflows to new versions
49+
50+
51+
## Important Notes
52+
53+
- **Single Workflow Limitation**: This sample allows only one workflow at a time to simplify the signal handling mechanism. In production, you would typically handle multiple workflows.
54+
- **Signal Method**: The workflow uses a simple signal method to stop gracefully, keeping the implementation straightforward.
55+
- **Breaking Changes**: V4 demonstrates what happens when you introduce a breaking change - workflows started by V1 cannot be executed.
56+
57+
## Version Compatibility Matrix
58+
59+
| Started By | V1 Worker | V2 Worker | V3 Worker | V4 Worker |
60+
|------------|-----------|-----------|-----------|-----------|
61+
| V1 |||||
62+
| V2 |||||
63+
| V3 |||||
64+
| V4 |||||
65+
66+
## Running the Example
67+
68+
### Prerequisites
69+
70+
Make sure you have Cadence server running and the sample compiled:
71+
72+
```bash
73+
# Build the sample
74+
go build -o bin/versioning cmd/samples/recipes/versioning/*.go
75+
```
76+
77+
### Step-by-Step Deployment Simulation
78+
79+
#### 1. Start Worker V1
80+
```bash
81+
./bin/versioning -m worker -v 1
82+
```
83+
84+
#### 2. Trigger a Workflow
85+
```bash
86+
./bin/versioning -m trigger
87+
```
88+
89+
Wait for logs in the V1 worker to ensure that a workflow has been executed by worker V1.
90+
91+
#### 3. Deploy Worker V2
92+
Let's simulate a deployment from V1 to V2 and run a V2 worker alongside the V1 worker:
93+
94+
```bash
95+
./bin/versioning -m worker -v 2
96+
```
97+
98+
The workflow should still be executed by worker V1.
99+
100+
#### 4. Test V2 Compatibility
101+
Let's simulate that worker V1 is shut down and the workflow will be rescheduled to the V2 worker:
102+
* Kill the process of worker V1 (Ctrl+C), then wait 5 seconds to see workflow rescheduling to worker V2 without errors.
103+
104+
Verify logs of the V2 worker - it should handle the workflow started by V1.
105+
106+
#### 5. Upgrade to Version V3
107+
Let's continue the deployment and upgrade to V3, running a V3 worker alongside the V2 worker:
108+
109+
```bash
110+
./bin/versioning -m worker -v 3
111+
```
112+
113+
The workflow should still be executed by worker V2.
114+
115+
#### 6. Test V3 Compatibility
116+
Let's simulate that worker V2 is shut down and the workflow will be rescheduled to the V3 worker:
117+
118+
* Kill the process of worker V2, then wait 5 seconds to see workflow rescheduling to worker V3 without errors.
119+
120+
Verify logs of the V3 worker - it should handle the workflow started by V2.
121+
122+
#### 7. Gracefully Stop the Workflow
123+
Before upgrading to V4, we should ensure that the workflow has been stopped, otherwise it will fail. For this, we need to send a signal to stop it gracefully:
124+
125+
```bash
126+
./bin/versioning -m stop
127+
```
128+
129+
You should see that the workflow has been stopped.
130+
131+
#### 8. Start a New Workflow
132+
Let's start a new workflow:
133+
134+
```bash
135+
./bin/versioning -m trigger
136+
```
137+
138+
The workflow will use version 1 of the change ID (V3's and V4's default).
139+
140+
#### 9. Rollback to Worker V2
141+
Let's imagine that V3 has an issue and we need to rollback to V2. Let's start a worker V2:
142+
143+
```bash
144+
./bin/versioning -m worker -v 2
145+
```
146+
147+
* Kill the process of worker V3, then wait for workflow rescheduling.
148+
* Verify logs of V2 worker - V2 worker should handle workflows started by V3.
149+
150+
#### 10. Aggressive Upgrade: V2 to V4 (Breaking Change)
151+
We decide to combine getting rid of support for V1 and make an upgrade straightforward to V4:
152+
153+
```bash
154+
./bin/versioning -m worker -v 4
155+
```
156+
157+
* Kill the process of worker V2, then wait for workflow rescheduling.
158+
* Verify logs of V4 worker - V4 worker should handle workflows started by V4.
159+
160+
161+
## Command Reference
162+
163+
```bash
164+
# Start a worker with specific version
165+
./bin/versioning -m worker -v <version>
166+
167+
# Start a new workflow
168+
./bin/versioning -m trigger
169+
170+
# Stop the running workflow
171+
./bin/versioning -m stop
172+
```
173+
174+
Where `<version>` can be:
175+
- `1` or `v1` - Version 1 (FooActivity only, DefaultVersion)
176+
- `2` or `v2` - Version 2 (FooActivity + BarActivity, DefaultVersion)
177+
- `3` or `v3` - Version 3 (FooActivity + BarActivity, Version #1)
178+
- `4` or `v4` - Version 4 (BarActivity only, Version #1)
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
"os"
7+
"time"
8+
9+
"go.uber.org/cadence/client"
10+
"go.uber.org/cadence/worker"
11+
12+
"github.com/uber-common/cadence-samples/cmd/samples/common"
13+
)
14+
15+
// This needs to be done as part of a bootstrap step when the process starts.
16+
// The workers are supposed to be long running.
17+
func startWorkers(h *common.SampleHelper) worker.Worker {
18+
// Configure worker options.
19+
workerOptions := worker.Options{
20+
MetricsScope: h.WorkerMetricScope,
21+
Logger: h.Logger,
22+
WorkerStopTimeout: 1 * time.Second,
23+
}
24+
return h.StartWorkers(h.Config.DomainName, ApplicationName, workerOptions)
25+
}
26+
27+
func startWorkflow(h *common.SampleHelper) {
28+
// Allow to run only one Versioned workflow at a time
29+
workflowOptions := client.StartWorkflowOptions{
30+
ID: VersionedWorkflowID,
31+
TaskList: ApplicationName,
32+
ExecutionStartToCloseTimeout: time.Hour,
33+
DecisionTaskStartToCloseTimeout: time.Minute,
34+
WorkflowIDReusePolicy: client.WorkflowIDReusePolicyAllowDuplicate,
35+
}
36+
h.StartWorkflow(workflowOptions, VersionedWorkflowName, 0)
37+
}
38+
39+
// stopWorkflow sends a signal to the workflow to stop it gracefully.
40+
func stopWorkflow(h *common.SampleHelper) {
41+
h.Logger.Info("Stopping workflow")
42+
h.SignalWorkflow(VersionedWorkflowID, StopSignalName, "")
43+
}
44+
45+
func main() {
46+
var mode string
47+
var version string
48+
49+
flag.StringVar(&mode, "m", "trigger", "Mode is worker (version flag is required), trigger (start a new workflow, only one allowed), stop (stop a running workflow). Default is trigger.")
50+
flag.StringVar(&version, "v", "", "Version of the workflow to run, supported versions are 1, 2, 3, or 4. Required in worker mode.")
51+
52+
flag.Parse()
53+
54+
var h common.SampleHelper
55+
h.SetupServiceConfig()
56+
57+
switch mode {
58+
case "worker":
59+
switch version {
60+
case "1", "v1":
61+
SetupHelperForVersionedWorkflowV1(&h)
62+
63+
case "2", "v2":
64+
SetupHelperForVersionedWorkflowV2(&h)
65+
66+
case "3", "v3":
67+
SetupHelperForVersionedWorkflowV3(&h)
68+
69+
case "4", "v4":
70+
SetupHelperForVersionedWorkflowV4(&h)
71+
72+
case "":
73+
fmt.Printf("-v flag is required for worker mode. Use -v 1, -v 2, -v 3, or -v 4 to specify the version.\n")
74+
os.Exit(1)
75+
76+
default:
77+
fmt.Printf("Invalid version specified:%s . Use -v 1, -v 2, -v 3, or -v 4.", version)
78+
os.Exit(1)
79+
}
80+
81+
startWorkers(&h)
82+
83+
// The workers are supposed to be long-running process that should not exit.
84+
// Use select{} to block indefinitely for samples, you can quit by CMD+C.
85+
select {}
86+
87+
case "trigger":
88+
startWorkflow(&h)
89+
90+
case "stop":
91+
stopWorkflow(&h)
92+
93+
default:
94+
fmt.Printf("Invalid mode specified: %s. Use -m worker, -m trigger, -m stop.\n", mode)
95+
os.Exit(1)
96+
}
97+
}

0 commit comments

Comments
 (0)