From da4f3b0d392039d2480fa43ba2389a190899b02d Mon Sep 17 00:00:00 2001 From: Seva Kaloshin Date: Mon, 7 Jul 2025 11:06:45 +0200 Subject: [PATCH 1/4] bump cadence-go-client up to 1.2.10-rc.16 --- go.mod | 4 ++-- go.sum | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index e438b88d..531f70a7 100644 --- a/go.mod +++ b/go.mod @@ -12,9 +12,9 @@ require ( github.com/pkg/errors v0.9.1 github.com/stretchr/testify v1.9.0 github.com/uber-go/tally v3.4.3+incompatible - github.com/uber/cadence-idl v0.0.0-20241126065313-57bd6876d48f + github.com/uber/cadence-idl v0.0.0-20250616185004-cc6f52f87bc6 github.com/uber/jaeger-client-go v2.30.0+incompatible - go.uber.org/cadence v1.2.10-rc.13 + go.uber.org/cadence v1.2.10-rc.16 go.uber.org/multierr v1.6.0 go.uber.org/yarpc v1.60.0 go.uber.org/zap v1.23.0 diff --git a/go.sum b/go.sum index cbe488a9..77d860af 100644 --- a/go.sum +++ b/go.sum @@ -219,6 +219,7 @@ github.com/uber-go/tally v3.4.3+incompatible h1:Oq25FXV8cWHPRo+EPeNdbN3LfuozC9mD github.com/uber-go/tally v3.4.3+incompatible/go.mod h1:YDTIBxdXyOU/sCWilKB4bgyufu1cEi0jdVnRdxvjnmU= github.com/uber/cadence-idl v0.0.0-20241126065313-57bd6876d48f h1:U2nI6IKh80rrueDb2G3wuhCkCHYCsLp9EFBazeTs7Dk= github.com/uber/cadence-idl v0.0.0-20241126065313-57bd6876d48f/go.mod h1:oyUK7GCNCRHCCyWyzifSzXpVrRYVBbAMHAzF5dXiKws= +github.com/uber/cadence-idl v0.0.0-20250616185004-cc6f52f87bc6/go.mod h1:oyUK7GCNCRHCCyWyzifSzXpVrRYVBbAMHAzF5dXiKws= github.com/uber/jaeger-client-go v2.22.1+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o= github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= @@ -241,6 +242,8 @@ go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/cadence v1.2.10-rc.13 h1:/OllgtrebYvEzuRUQw9qfriXMCGSxubM2PSoNJSVtS8= go.uber.org/cadence v1.2.10-rc.13/go.mod h1:tQ27w3gu3dqw2SAQFBRdvkjNAsf7G63WblnLbOFdYbU= +go.uber.org/cadence v1.2.10-rc.16 h1:fb4UzDApVPwkflYgM+OtS4nkyDDH2iR9KFMRq0EI5/w= +go.uber.org/cadence v1.2.10-rc.16/go.mod h1:/Lv4o2eahjro+LKjXmuYm20wg5+84t+h2pu0xm7gUfM= go.uber.org/dig v1.8.0/go.mod h1:X34SnWGr8Fyla9zQNO2GSO2D+TIuqB14OS8JhYocIyw= go.uber.org/dig v1.10.0/go.mod h1:X34SnWGr8Fyla9zQNO2GSO2D+TIuqB14OS8JhYocIyw= go.uber.org/dig v1.17.0 h1:5Chju+tUvcC+N7N6EV08BJz41UZuO3BmHcN4A287ZLI= From 142c1ea08810c349d8baf9ef139731f811ca0d39 Mon Sep 17 00:00:00 2001 From: Seva Kaloshin Date: Mon, 7 Jul 2025 12:38:18 +0200 Subject: [PATCH 2/4] bump cadence-go-client up to 1.2.10-rc.16 --- go.mod | 2 +- go.sum | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 531f70a7..c5b7034e 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,6 @@ require ( github.com/uber/cadence-idl v0.0.0-20250616185004-cc6f52f87bc6 github.com/uber/jaeger-client-go v2.30.0+incompatible go.uber.org/cadence v1.2.10-rc.16 - go.uber.org/multierr v1.6.0 go.uber.org/yarpc v1.60.0 go.uber.org/zap v1.23.0 gopkg.in/yaml.v2 v2.4.0 @@ -55,6 +54,7 @@ require ( go.uber.org/atomic v1.11.0 // indirect go.uber.org/dig v1.17.0 // indirect go.uber.org/fx v1.13.1 // indirect + go.uber.org/multierr v1.6.0 // indirect go.uber.org/net/metrics v1.3.0 // indirect go.uber.org/thriftrw v1.29.2 // indirect golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e // indirect diff --git a/go.sum b/go.sum index 77d860af..38a34e26 100644 --- a/go.sum +++ b/go.sum @@ -217,8 +217,7 @@ github.com/uber-go/tally v3.3.12+incompatible/go.mod h1:YDTIBxdXyOU/sCWilKB4bgyu github.com/uber-go/tally v3.3.15+incompatible/go.mod h1:YDTIBxdXyOU/sCWilKB4bgyufu1cEi0jdVnRdxvjnmU= github.com/uber-go/tally v3.4.3+incompatible h1:Oq25FXV8cWHPRo+EPeNdbN3LfuozC9mDK2/4vZ1k38U= github.com/uber-go/tally v3.4.3+incompatible/go.mod h1:YDTIBxdXyOU/sCWilKB4bgyufu1cEi0jdVnRdxvjnmU= -github.com/uber/cadence-idl v0.0.0-20241126065313-57bd6876d48f h1:U2nI6IKh80rrueDb2G3wuhCkCHYCsLp9EFBazeTs7Dk= -github.com/uber/cadence-idl v0.0.0-20241126065313-57bd6876d48f/go.mod h1:oyUK7GCNCRHCCyWyzifSzXpVrRYVBbAMHAzF5dXiKws= +github.com/uber/cadence-idl v0.0.0-20250616185004-cc6f52f87bc6 h1:YJlEu9Unzifwdn6SuE4rrl4zJ5lop5gBfSX8AyodTww= github.com/uber/cadence-idl v0.0.0-20250616185004-cc6f52f87bc6/go.mod h1:oyUK7GCNCRHCCyWyzifSzXpVrRYVBbAMHAzF5dXiKws= github.com/uber/jaeger-client-go v2.22.1+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o= @@ -240,8 +239,6 @@ go.uber.org/atomic v1.5.1/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -go.uber.org/cadence v1.2.10-rc.13 h1:/OllgtrebYvEzuRUQw9qfriXMCGSxubM2PSoNJSVtS8= -go.uber.org/cadence v1.2.10-rc.13/go.mod h1:tQ27w3gu3dqw2SAQFBRdvkjNAsf7G63WblnLbOFdYbU= go.uber.org/cadence v1.2.10-rc.16 h1:fb4UzDApVPwkflYgM+OtS4nkyDDH2iR9KFMRq0EI5/w= go.uber.org/cadence v1.2.10-rc.16/go.mod h1:/Lv4o2eahjro+LKjXmuYm20wg5+84t+h2pu0xm7gUfM= go.uber.org/dig v1.8.0/go.mod h1:X34SnWGr8Fyla9zQNO2GSO2D+TIuqB14OS8JhYocIyw= From 1fbe7129b6b49706435ad1b4db66fc3d84895fd6 Mon Sep 17 00:00:00 2001 From: Seva Kaloshin Date: Mon, 7 Jul 2025 15:31:51 +0200 Subject: [PATCH 3/4] add versioning to samples --- Makefile | 5 + README.md | 3 + cmd/samples/common/sample_helper.go | 4 +- cmd/samples/recipes/versioning/README.md | 163 ++++++++++++++++ cmd/samples/recipes/versioning/main.go | 97 ++++++++++ .../recipes/versioning/versioned_workflow.go | 174 ++++++++++++++++++ 6 files changed, 445 insertions(+), 1 deletion(-) create mode 100644 cmd/samples/recipes/versioning/README.md create mode 100644 cmd/samples/recipes/versioning/main.go create mode 100644 cmd/samples/recipes/versioning/versioned_workflow.go diff --git a/Makefile b/Makefile index ab4d1cf8..81da9515 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,7 @@ export PATH := $(GOPATH)/bin:$(PATH) default: test PROGS = helloworld \ + versioning \ delaystart \ branch \ childworkflow \ @@ -172,7 +173,11 @@ crossdomain-run: crossdomain sideeffect: go build -o bin/sideeffect cmd/samples/recipes/sideeffect/*.go +versioning: + go build -o bin/versioning cmd/samples/recipes/versioning/*.go + bins: helloworld \ + versioning \ delaystart \ branch \ crossdomain \ diff --git a/README.md b/README.md index 29285080..2189b20b 100644 --- a/README.md +++ b/README.md @@ -161,6 +161,9 @@ See more details in https://github.com/uber-common/cadence-samples/blob/master/c #### recovery See more details in https://github.com/uber-common/cadence-samples/blob/master/cmd/samples/recovery/README.md +#### versioning +See more details in https://github.com/uber-common/cadence-samples/blob/master/cmd/samples/versioning/README.md + ## License Apache 2.0 License, please see [LICENSE](https://github.com/cadence-workflow/cadence-samples/blob/master/LICENSE) for details. \ No newline at end of file diff --git a/cmd/samples/common/sample_helper.go b/cmd/samples/common/sample_helper.go index 4ac469c5..7ed765a0 100644 --- a/cmd/samples/common/sample_helper.go +++ b/cmd/samples/common/sample_helper.go @@ -245,7 +245,7 @@ func (h *SampleHelper) RegisterActivityWithAlias(activity interface{}, alias str } // StartWorkers starts workflow worker and activity worker based on configured options. -func (h *SampleHelper) StartWorkers(domainName string, groupName string, options worker.Options) { +func (h *SampleHelper) StartWorkers(domainName string, groupName string, options worker.Options) worker.Worker { worker := worker.New(h.Service, domainName, groupName, options) h.registerWorkflowAndActivity(worker) @@ -254,6 +254,8 @@ func (h *SampleHelper) StartWorkers(domainName string, groupName string, options h.Logger.Error("Failed to start workers.", zap.Error(err)) panic("Failed to start workers") } + + return worker } func (h *SampleHelper) QueryWorkflow(workflowID, runID, queryType string, args ...interface{}) { diff --git a/cmd/samples/recipes/versioning/README.md b/cmd/samples/recipes/versioning/README.md new file mode 100644 index 00000000..1f8b2605 --- /dev/null +++ b/cmd/samples/recipes/versioning/README.md @@ -0,0 +1,163 @@ +# Versioning Workflow Example + +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. + +## Overview + +The versioning sample implements a workflow that evolves through multiple versions (V1 → V2 → V3 → V4) with rollbacks, demonstrating: + +- **Safe Deployment**: How to deploy new workflow versions without breaking existing executions +- **Backward Compatibility**: How to handle workflows started with older versions +- **Rollback Capability**: How to safely rollback to previous versions +- **Version Isolation**: How different versions can execute different logic paths + +## Workflow Versions + +### Version 1 (V1) +- Executes `FooActivity` only +- Uses `workflow.DefaultVersion` for the change ID + +### Version 2 (V2) +- Supports both `FooActivity` and `BarActivity` +- Uses `workflow.GetVersion()` with `workflow.ExecuteWithMinVersion()` to handle both old and new workflows +- Workflows started by V1 continue using `FooActivity` + +### Version 3 (V3) +- Similar to V2 but uses standard `workflow.GetVersion()` (without `ExecuteWithMinVersion`) +- All new workflows use version 1 of the change ID + +### Version 4 (V4) +- Only supports `BarActivity` +- Forces all workflows to use version 1 of the change ID +- **Breaking change**: Cannot execute workflows started by V1 + +## Key Cadence APIs Used + +- `workflow.GetVersion()`: Determines which version of code to execute +- `workflow.ExecuteWithVersion()`: Executes code with a specific version +- `workflow.ExecuteWithMinVersion()`: Executes code with minimum version requirement +- `workflow.DefaultVersion`: Represents the original version before any changes + +## Safe Deployment Flow + +This example demonstrates a safe deployment strategy that allows you to: + +1. **Deploy new versions** while keeping old workers running +2. **Test compatibility** before fully switching over +3. **Rollback safely** if issues are discovered +4. **Gradually migrate** workflows to new versions + +## Running the Example + +### Prerequisites + +Make sure you have Cadence server running and the sample compiled: + +```bash +# Build the sample +go build -o bin/versioning cmd/samples/recipes/versioning/*.go +``` + +### Step-by-Step Deployment Simulation + +#### 1. Start Worker V1 +```bash +./bin/versioning -m worker -v 1 +``` + +#### 2. Trigger a Workflow (Executed by Worker V1) +```bash +./bin/versioning -m trigger +``` + +**Deployment started** - You now have a workflow running on V1. + +#### 3. Deploy Worker V2 (Safe Deployment) +```bash +./bin/versioning -m worker -v 2 +``` + +#### 4. Test V2 Compatibility +* Kill the process of worker V1 (Ctrl+C), then wait 5 seconds to see workflow rescheduling to worker V2 without errors. +* Verify logs of V2, V2 should handle workflows started by V1. + +#### 5. Upgrade to Version V3 +```bash +./bin/versioning -m worker -v 3 +``` + +#### 6. Test V3 Compatibility +* Kill the process of worker V2, then wait 5 seconds to see workflow rescheduling to worker V3 without errors. +* Verify logs of V3 worker, V3 worker should handle workflows started by V1. + +#### 7. Test Breaking Change with V4 +```bash +./bin/versioning -m worker -v 4 +``` + +Kill the process of worker V3. You'll notice that workflows initiated by V1 cannot be executed by version V4 - this simulates a breaking change. + +#### 8. Gracefully Stop the Workflow +```bash +./bin/versioning -m stop +``` + +#### 9. Start a New Workflow (Will Use V4 Logic) +```bash +./bin/versioning -m trigger +``` + +The workflow will use version 1 of the change ID (V4's default). + +#### 10. Rollback to Worker V2 +```bash +./bin/versioning -m worker -v 2 +``` + +* Kill the process of worker V4, then wait for workflow rescheduling. +* Verify logs of V2 worker, V2 worker should handle workflows started by V4. + +#### 11. Aggressive Upgrade: V2 to V4 (Breaking Change) +Since V3 worked fine, we decide to combine getting rid of support for V1 and make an upgrade straightforward to V4: + +```bash +./bin/versioning -m worker -v 4 +``` + +* Kill the process of worker V2, then wait for workflow rescheduling. +* Verify logs of V4 worker, V4 worker should handle workflows started by V4. + +## Important Notes + +- **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. +- **Signal Method**: The workflow uses a simple signal method to stop gracefully, keeping the implementation straightforward. +- **Version Compatibility**: Each version is designed to handle workflows started by compatible previous versions. +- **Breaking Changes**: V4 demonstrates what happens when you introduce a breaking change - workflows started by V1 cannot be executed. + +## Version Compatibility Matrix + +| Started By | V1 Worker | V2 Worker | V3 Worker | V4 Worker | +|------------|-----------|-----------|-----------|-----------| +| V1 | ✅ | ✅ | ✅ | ❌ | +| V2 | ❌ | ✅ | ✅ | ✅ | +| V3 | ❌ | ✅ | ✅ | ✅ | +| V4 | ❌ | ✅ | ✅ | ✅ | + +## Command Reference + +```bash +# Start a worker with specific version +./bin/versioning -m worker -v + +# Start a new workflow +./bin/versioning -m trigger + +# Stop the running workflow +./bin/versioning -m stop +``` + +Where `` can be: +- `1` or `v1` - Version 1 (FooActivity only, DefaultVersion) +- `2` or `v2` - Version 2 (FooActivity + BarActivity, DefaultVersion) +- `3` or `v3` - Version 3 (FooActivity + BarActivity, Version #1) +- `4` or `v4` - Version 4 (BarActivity only, Version #1) diff --git a/cmd/samples/recipes/versioning/main.go b/cmd/samples/recipes/versioning/main.go new file mode 100644 index 00000000..9ebd9a2d --- /dev/null +++ b/cmd/samples/recipes/versioning/main.go @@ -0,0 +1,97 @@ +package main + +import ( + "flag" + "fmt" + "os" + "time" + + "go.uber.org/cadence/client" + "go.uber.org/cadence/worker" + + "github.com/uber-common/cadence-samples/cmd/samples/common" +) + +// This needs to be done as part of a bootstrap step when the process starts. +// The workers are supposed to be long running. +func startWorkers(h *common.SampleHelper) worker.Worker { + // Configure worker options. + workerOptions := worker.Options{ + MetricsScope: h.WorkerMetricScope, + Logger: h.Logger, + WorkerStopTimeout: 1 * time.Second, + } + return h.StartWorkers(h.Config.DomainName, ApplicationName, workerOptions) +} + +func startWorkflow(h *common.SampleHelper) { + // Allow to run only one Versioned workflow at a time + workflowOptions := client.StartWorkflowOptions{ + ID: VersionedWorkflowID, + TaskList: ApplicationName, + ExecutionStartToCloseTimeout: time.Hour, + DecisionTaskStartToCloseTimeout: time.Minute, + WorkflowIDReusePolicy: client.WorkflowIDReusePolicyAllowDuplicate, + } + h.StartWorkflow(workflowOptions, VersionedWorkflowName, 0) +} + +// stopWorkflow sends a signal to the workflow to stop it gracefully. +func stopWorkflow(h *common.SampleHelper) { + h.Logger.Info("Stopping workflow") + h.SignalWorkflow(VersionedWorkflowID, StopSignalName, "") +} + +func main() { + var mode string + var version string + + 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.") + flag.StringVar(&version, "v", "", "Version of the workflow to run, supported versions are 1, 2, 3, or 4. Required in worker mode.") + + flag.Parse() + + var h common.SampleHelper + h.SetupServiceConfig() + + switch mode { + case "worker": + switch version { + case "1", "v1": + SetupHelperForVersionedWorkflowV1(&h) + + case "2", "v2": + SetupHelperForVersionedWorkflowV2(&h) + + case "3", "v3": + SetupHelperForVersionedWorkflowV3(&h) + + case "4", "v4": + SetupHelperForVersionedWorkflowV4(&h) + + case "": + fmt.Printf("-v flag is required for worker mode. Use -v 1, -v 2, -v 3, or -v 4 to specify the version.\n") + os.Exit(1) + + default: + fmt.Printf("Invalid version specified:%s . Use -v 1, -v 2, -v 3, or -v 4.", version) + os.Exit(1) + } + + startWorkers(&h) + + // The workers are supposed to be long-running process that should not exit. + // Use select{} to block indefinitely for samples, you can quit by CMD+C. + select {} + + case "trigger": + startWorkflow(&h) + + case "stop": + stopWorkflow(&h) + + default: + fmt.Printf("Invalid mode specified: %s. Use -m worker, -m trigger, -m stop.\n", mode) + os.Exit(1) + } +} diff --git a/cmd/samples/recipes/versioning/versioned_workflow.go b/cmd/samples/recipes/versioning/versioned_workflow.go new file mode 100644 index 00000000..ab5721a7 --- /dev/null +++ b/cmd/samples/recipes/versioning/versioned_workflow.go @@ -0,0 +1,174 @@ +package main + +import ( + "context" + "github.com/uber-common/cadence-samples/cmd/samples/common" + "go.uber.org/cadence/activity" + "go.uber.org/cadence/workflow" + "go.uber.org/zap" + "time" +) + +/** + * This sample workflow continuously counting signals and do continue as new + */ + +const ( + // ApplicationName is the task list for this sample + ApplicationName = "versioning" + + // TestChangeID is a constant used to identify the version change in the workflow. + TestChangeID = "test-change" + + // FooActivityName and BarActivityName are the names of the activities used in the workflows. + FooActivityName = "FooActivity" + BarActivityName = "BarActivity" + + // VersionedWorkflowName is the name of the versioned workflow. + VersionedWorkflowName = "VersionedWorkflow" + + // VersionedWorkflowID is the ID of the versioned workflow. + VersionedWorkflowID = "versioned_workflow" + + // StopSignalName is the name of the signal used to stop the workflow to finish it successfully + StopSignalName = "StopSignal" +) + +const ( + V1 int32 = iota + 1 + V2 + V3 + V4 +) + +var activityOptions = workflow.ActivityOptions{ + ScheduleToStartTimeout: time.Minute, + StartToCloseTimeout: time.Minute, + HeartbeatTimeout: time.Second * 20, +} + +// VersionedWorkflowV1 is the first version of the workflow, supports only DefaultVersion. +// All workflows started by this version will have the change ID set to DefaultVersion. +func VersionedWorkflowV1(ctx workflow.Context) error { + ctx = workflow.WithActivityOptions(ctx, activityOptions) + + err := workflow.ExecuteActivity(ctx, FooActivityName).Get(ctx, nil) + if err != nil { + return err + } + + return waitForSignal(ctx, V1) +} + +// VersionedWorkflowV2 is the second version of the workflow, supports DefaultVersion and 1 +// All workflows started by this version will have the change ID set to DefaultVersion. +func VersionedWorkflowV2(ctx workflow.Context) error { + ctx = workflow.WithActivityOptions(ctx, activityOptions) + + var err error + var version workflow.Version + + version = workflow.GetVersion(ctx, TestChangeID, workflow.DefaultVersion, 1, workflow.ExecuteWithMinVersion()) + if version == workflow.DefaultVersion { + err = workflow.ExecuteActivity(ctx, FooActivityName).Get(ctx, nil) + } else { + err = workflow.ExecuteActivity(ctx, BarActivityName).Get(ctx, nil) + } + if err != nil { + return err + } + + return waitForSignal(ctx, V2) +} + +// VersionedWorkflowV3 is the third version of the workflow, supports DefaultVersion and 1 +// All workflows started by this version will have the change ID set to 1. +func VersionedWorkflowV3(ctx workflow.Context) error { + ctx = workflow.WithActivityOptions(ctx, activityOptions) + + var err error + var version workflow.Version + + version = workflow.GetVersion(ctx, TestChangeID, workflow.DefaultVersion, 1) + if version == workflow.DefaultVersion { + err = workflow.ExecuteActivity(ctx, FooActivityName).Get(ctx, nil) + } else { + err = workflow.ExecuteActivity(ctx, BarActivityName).Get(ctx, nil) + } + if err != nil { + return err + } + + return waitForSignal(ctx, V3) +} + +// VersionedWorkflowV4 is the fourth version of the workflow, supports only version 1 +// All workflows started by this version will have the change ID set to 1. +func VersionedWorkflowV4(ctx workflow.Context) error { + ctx = workflow.WithActivityOptions(ctx, activityOptions) + + workflow.GetVersion(ctx, TestChangeID, 1, 1) + err := workflow.ExecuteActivity(ctx, BarActivityName).Get(ctx, nil) + if err != nil { + return err + } + + return waitForSignal(ctx, V4) +} + +func waitForSignal(ctx workflow.Context, version int32) error { + workflow.GetLogger(ctx).Info("Waiting for signal", zap.Int32("Worker Version", version)) + + signalCh := workflow.GetSignalChannel(ctx, StopSignalName) + + for { + var signal string + if signalCh.ReceiveAsync(&signal) { + break + } + + workflow.GetLogger(ctx).Info("No signal received yet, continuing to wait...", zap.Int32("Worker Version", version)) + workflow.Sleep(ctx, time.Second*5) + } + + workflow.GetLogger(ctx).Info("Got the signal, finishing the workflow", zap.Int32("Worker Version", version)) + return nil +} + +// SetupHelperForVersionedWorkflowV1 registers VersionedWorkflowV1 and FooActivity +func SetupHelperForVersionedWorkflowV1(h *common.SampleHelper) { + h.RegisterWorkflowWithAlias(VersionedWorkflowV1, VersionedWorkflowName) + h.RegisterActivityWithAlias(FooActivity, FooActivityName) +} + +// SetupHelperForVersionedWorkflowV2 registers VersionedWorkflowV2, FooActivity, and BarActivity +func SetupHelperForVersionedWorkflowV2(h *common.SampleHelper) { + h.RegisterWorkflowWithAlias(VersionedWorkflowV2, VersionedWorkflowName) + h.RegisterActivityWithAlias(FooActivity, FooActivityName) + h.RegisterActivityWithAlias(BarActivity, BarActivityName) +} + +// SetupHelperForVersionedWorkflowV3 registers VersionedWorkflowV3, FooActivity, and BarActivity +func SetupHelperForVersionedWorkflowV3(h *common.SampleHelper) { + h.RegisterWorkflowWithAlias(VersionedWorkflowV3, VersionedWorkflowName) + h.RegisterActivityWithAlias(FooActivity, FooActivityName) + h.RegisterActivityWithAlias(BarActivity, BarActivityName) +} + +// SetupHelperForVersionedWorkflowV4 registers VersionedWorkflowV4 and BarActivity +func SetupHelperForVersionedWorkflowV4(h *common.SampleHelper) { + h.RegisterWorkflowWithAlias(VersionedWorkflowV4, VersionedWorkflowName) + h.RegisterActivityWithAlias(BarActivity, BarActivityName) +} + +// FooActivity returns "foo" as a result of the activity execution. +func FooActivity(ctx context.Context) (string, error) { + activity.GetLogger(ctx).Info("Executing FooActivity") + return "foo", nil +} + +// BarActivity returns "bar" as a result of the activity execution. +func BarActivity(ctx context.Context) (string, error) { + activity.GetLogger(ctx).Info("Executing BarActivity") + return "bar", nil +} From e1fe25118be9fc26e6bfa029ccf59dc78342eb88 Mon Sep 17 00:00:00 2001 From: Seva Kaloshin Date: Mon, 7 Jul 2025 15:49:09 +0200 Subject: [PATCH 4/4] change some structure --- cmd/samples/recipes/versioning/README.md | 83 ++++++++++++++---------- 1 file changed, 49 insertions(+), 34 deletions(-) diff --git a/cmd/samples/recipes/versioning/README.md b/cmd/samples/recipes/versioning/README.md index 1f8b2605..9eb6e070 100644 --- a/cmd/samples/recipes/versioning/README.md +++ b/cmd/samples/recipes/versioning/README.md @@ -47,6 +47,22 @@ This example demonstrates a safe deployment strategy that allows you to: 3. **Rollback safely** if issues are discovered 4. **Gradually migrate** workflows to new versions + +## Important Notes + +- **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. +- **Signal Method**: The workflow uses a simple signal method to stop gracefully, keeping the implementation straightforward. +- **Breaking Changes**: V4 demonstrates what happens when you introduce a breaking change - workflows started by V1 cannot be executed. + +## Version Compatibility Matrix + +| Started By | V1 Worker | V2 Worker | V3 Worker | V4 Worker | +|------------|-----------|-----------|-----------|-----------| +| V1 | ✅ | ✅ | ✅ | ❌ | +| V2 | ❌ | ✅ | ✅ | ✅ | +| V3 | ❌ | ✅ | ✅ | ✅ | +| V4 | ❌ | ✅ | ✅ | ✅ | + ## Running the Example ### Prerequisites @@ -65,83 +81,82 @@ go build -o bin/versioning cmd/samples/recipes/versioning/*.go ./bin/versioning -m worker -v 1 ``` -#### 2. Trigger a Workflow (Executed by Worker V1) +#### 2. Trigger a Workflow ```bash ./bin/versioning -m trigger ``` -**Deployment started** - You now have a workflow running on V1. +Wait for logs in the V1 worker to ensure that a workflow has been executed by worker V1. + +#### 3. Deploy Worker V2 +Let's simulate a deployment from V1 to V2 and run a V2 worker alongside the V1 worker: -#### 3. Deploy Worker V2 (Safe Deployment) ```bash ./bin/versioning -m worker -v 2 ``` +The workflow should still be executed by worker V1. + #### 4. Test V2 Compatibility +Let's simulate that worker V1 is shut down and the workflow will be rescheduled to the V2 worker: * Kill the process of worker V1 (Ctrl+C), then wait 5 seconds to see workflow rescheduling to worker V2 without errors. -* Verify logs of V2, V2 should handle workflows started by V1. + +Verify logs of the V2 worker - it should handle the workflow started by V1. #### 5. Upgrade to Version V3 +Let's continue the deployment and upgrade to V3, running a V3 worker alongside the V2 worker: + ```bash ./bin/versioning -m worker -v 3 ``` +The workflow should still be executed by worker V2. + #### 6. Test V3 Compatibility +Let's simulate that worker V2 is shut down and the workflow will be rescheduled to the V3 worker: + * Kill the process of worker V2, then wait 5 seconds to see workflow rescheduling to worker V3 without errors. -* Verify logs of V3 worker, V3 worker should handle workflows started by V1. -#### 7. Test Breaking Change with V4 -```bash -./bin/versioning -m worker -v 4 -``` +Verify logs of the V3 worker - it should handle the workflow started by V2. -Kill the process of worker V3. You'll notice that workflows initiated by V1 cannot be executed by version V4 - this simulates a breaking change. +#### 7. Gracefully Stop the Workflow +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: -#### 8. Gracefully Stop the Workflow ```bash ./bin/versioning -m stop ``` -#### 9. Start a New Workflow (Will Use V4 Logic) +You should see that the workflow has been stopped. + +#### 8. Start a New Workflow +Let's start a new workflow: + ```bash ./bin/versioning -m trigger ``` -The workflow will use version 1 of the change ID (V4's default). +The workflow will use version 1 of the change ID (V3's and V4's default). + +#### 9. Rollback to Worker V2 +Let's imagine that V3 has an issue and we need to rollback to V2. Let's start a worker V2: -#### 10. Rollback to Worker V2 ```bash ./bin/versioning -m worker -v 2 ``` -* Kill the process of worker V4, then wait for workflow rescheduling. -* Verify logs of V2 worker, V2 worker should handle workflows started by V4. +* Kill the process of worker V3, then wait for workflow rescheduling. +* Verify logs of V2 worker - V2 worker should handle workflows started by V3. -#### 11. Aggressive Upgrade: V2 to V4 (Breaking Change) -Since V3 worked fine, we decide to combine getting rid of support for V1 and make an upgrade straightforward to V4: +#### 10. Aggressive Upgrade: V2 to V4 (Breaking Change) +We decide to combine getting rid of support for V1 and make an upgrade straightforward to V4: ```bash ./bin/versioning -m worker -v 4 ``` * Kill the process of worker V2, then wait for workflow rescheduling. -* Verify logs of V4 worker, V4 worker should handle workflows started by V4. - -## Important Notes +* Verify logs of V4 worker - V4 worker should handle workflows started by V4. -- **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. -- **Signal Method**: The workflow uses a simple signal method to stop gracefully, keeping the implementation straightforward. -- **Version Compatibility**: Each version is designed to handle workflows started by compatible previous versions. -- **Breaking Changes**: V4 demonstrates what happens when you introduce a breaking change - workflows started by V1 cannot be executed. - -## Version Compatibility Matrix - -| Started By | V1 Worker | V2 Worker | V3 Worker | V4 Worker | -|------------|-----------|-----------|-----------|-----------| -| V1 | ✅ | ✅ | ✅ | ❌ | -| V2 | ❌ | ✅ | ✅ | ✅ | -| V3 | ❌ | ✅ | ✅ | ✅ | -| V4 | ❌ | ✅ | ✅ | ✅ | ## Command Reference