Skip to content

Conversation

@arzonus
Copy link
Contributor

@arzonus arzonus commented Jun 17, 2025

What changed?

  • Added support for new version control options in workflow execution:
    • Introduced GetVersionOption interface and related options
    • Added ExecuteWithVersion option to use specific version instead of max supported version
    • Added ExecuteWithMinVersion option to use specific version instead of min supported version
  • Enhanced GetVersion function to support GetVersionOptions optional parameters
  • Added test coverage for new version control features
  • Updated documentation with detailed examples of version control usage
  • Made changes making SearchAttributes are updated and DecisionMarker is created only if version is not equal to DefaultVersion
  • GetVersion of testWorkflowEnvironmentImpl got support of new options ExecuteWithVersion and ExecuteWithMinVersion

Why?

This change improves the workflow versioning system by providing more granular control over version execution. The new options allow for safer rollouts of workflow changes by separating code deployment from feature activation

Testing framework must emulate Cadence worker to enable customer writing unit tests. Without the change, customers will not be able to start using new functions in their unit tests.

How did you test it?

  • Added additional test cases in GetVersion
  • Manual reproducing of the steps mentioned in description of ExecuteWithVersion function to reproduce a safe rollout.
  • Integration tests reproducing of the steps mentioned in description of ExecuteWithVersion function to reproduce a safe rollout.

Potential risks

  • Existing workflows using GetVersion without options will continue to work as before
  • If maxSupported version was used to be DefaultVersion, it created SearchAttributes and DecisionMarker, but the changes don't do anymore.

Detailed Description
This PR introduces a new version control system for workflows by adding GetVersionOption interface and two new options (ExecuteWithVersionand ExecuteWithMinVersion) to the existing GetVersion function, allowing for more granular control over workflow version execution and safer deployment strategies. The changes maintain backward compatibility while enabling a three-step deployment process that separates code deployment from feature activation, ensuring safe rollback capabilities and better version management.

Interface Changes

1. WorkflowInterceptor, WorkflowEnvironment, TestWorkflowEnvironment Interfaces
// Before
GetVersion(ctx Context, changeID string, minSupported, maxSupported Version) Version

// After
GetVersion(ctx Context, changeID string, minSupported, maxSupported Version, opts ...GetVersionOption) Version
  • Added variadic parameter opts ...GetVersionOption to support version control options
2. New Types
GetVersionOption Type
type GetVersionOption interface {
	apply(*getVersionConfig)
}
  • New function type for configuring GetVersion behavior
3. New Option Functions
ExecuteWithVersion
// ExecuteWithVersion returns a GetVersionOption that forces a specific version to be returned
// when executed for the first time, instead of returning maxSupported version.
func ExecuteWithVersion(version Version) GetVersionOption
ExecuteWithMinVersion
// ExecuteWithMinVersion returns a GetVersionOption that makes GetVersion return minSupported version
// when executed for the first time, instead of returning maxSupported version.
func ExecuteWithMinVersion() GetVersionOption
4. Version Selection Logic Changes
New Implementation
	switch {

	// GetVersion for changeID is called first time in replay mode, use DefaultVersion
	case wc.isReplay:
		version = DefaultVersion

	// If ExecuteWithVersion option is used, use the custom version provided
	case options.customVersion != nil:
		version = *options.customVersion

	// If ExecuteWithMinVersion option is set, use the minimum supported version
	case options.useMinVersion:
		version = minSupported

	// Otherwise, use the maximum supported version
	default:
		version = maxSupported
	}

Impact Analysis

  • Backward Compatibility:
    • New changes are fully backward-compatible
  • Forward Compatibility: [Analysis of forward compatibility]
    • If there is a usage of the new functions, there is no forward compatibility, as they have been introduced, so the client code will not be compiled.

Testing Plan

  • Unit Tests: [Do we have unit test covering the change?]
    • Unit tests covering the new functions have been added
  • Persistence Tests: [If the change is related to a data type which is persisted, do we have persistence tests covering the change?]
    • The changes are not related to a data type which is persisted
  • Integration Tests: [Do we have integration test covering the change?]
    • Yes, there are tests covering the full cycle of version upgrade. They uses the replayer to emulate replaying on workers supporting previous and newer versions of a workflow
  • Compatibility Tests: [Have we done tests to test the backward and forward compatibility?]
    • The updated GetVersion uses variadic argument GetVersionOption that makes the changes compatible with the previous signature. At the same time, testify.Mock supports accepting GetVersion without a variadic argument, so there is no need to change the mock.

Rollout Plan

  • What is the rollout plan?
    • The new functions will be available for usage immediately after the release of the library
  • Does the order of deployment matter?
    • No
  • Is it safe to rollback? Does the order of rollback matter?
    • If customers start to use, they will have to remove the usage of the functions, otherwise their code will not be compiled
  • Is there a kill switch to mitigate the impact immediately?
    • No

@codecov
Copy link

codecov bot commented Jun 19, 2025

Codecov Report

Attention: Patch coverage is 94.44444% with 2 lines in your changes missing coverage. Please review.

Project coverage is 82.65%. Comparing base (6e22a27) to head (7ad8431).

Files with missing lines Patch % Lines
internal/interceptors.go 0.00% 2 Missing ⚠️
Files with missing lines Coverage Δ
internal/internal_event_handlers.go 71.42% <100.00%> (+0.48%) ⬆️
internal/internal_worker_base.go 84.03% <ø> (+1.40%) ⬆️
internal/workflow.go 76.52% <100.00%> (+0.28%) ⬆️
internal/interceptors.go 0.00% <0.00%> (ø)

Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 6e22a27...7ad8431. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@arzonus arzonus marked this pull request as ready for review June 20, 2025 06:55
Copy link
Contributor

@3vilhamster 3vilhamster left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea, and the implementation is rather small.

But I remember that we wanted to introduce x/ folder for experiments, and this API could be experimental for now until we are fully committed on the final design and merge it to the main.


// UseMinVersion is used to force GetVersion to return minSupported version
// instead of maxSupported version. Set up via ExecuteWithMinVersion option.
UseMinVersion bool
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, it will allow avoiding very wordy:

GetVersion(ctx, workflow.DefaultVerion, 1, workflow.WithVersion(workflow.DefaultVerion))

Copy link
Member

@Groxx Groxx left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some small stuff, some not-so-small stuff, and I think I need to spend some more time to think through the implementation's details... but I think it's on the right path, and it's a low-level semantic that users can't really build on their own so it seems worth having. though it is kinda fiddly (but I think that's unavoidable).

And, generally speaking, docs need a fair bit of work I think. They refer to things that don't exist, leave gaps in some places, and tbh I kinda doubt that anyone new to versioning will know what to do from just reading the docs :\ I do think we need to get that into a relatively good state before merging, since users will roughly immediately be exposed to it.


this is kinda making me wonder if we should make an entirely new API for this, to just switch to functional options everywhere. like maybe

v := GetVersionV2(
  ctx, "name",                  // unchanged
  3,                            // desired version (equal to ExecuteWithVersion)
  workflow.MaxVersion(4),       // default..4 range
  workflow.VersionRange(-1, 4), // or an explicit min/max
  // if omitted, no range allowed, same as `GetVersion(ctx, 3, 3)`
)

since I think we can't avoid an explicit "desired" version.

but we can also do that later. adding varargs like you've got here is probably the least-effort way to add features "in place" with no real migration needed - technically a breaking change, but not in a way most code will notice.

@arzonus arzonus force-pushed the add-options-to-get-version branch from 568b897 to 1f2e2ee Compare June 26, 2025 13:03
Copy link
Member

@Groxx Groxx left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, I think the behavior's correct, I was over-thinking it earlier. Let's do this 👍

I think comments summarize as:

  • some minor cleanups
  • can't expose internal.GetVersionOptions like that, probably just make an empty interface with a private method and alias it.
    • technically you can also cast to a same-shaped struct and re-implement or something. which is what should have been done for ~all of the things in these public packages. but that ship has sailed, more aliases is fine IMO since we can't remove the existing ones.
  • like one more test-suite test, to show that it behaves like the integration tests

otherwise looks good :)

@arzonus arzonus force-pushed the add-options-to-get-version branch from 055ac3d to b5257fc Compare June 27, 2025 12:43
Copy link
Member

@Groxx Groxx left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to go, thank you for the idea and all the effort! It's a good low-level-primitive to have, this was definitely a gap in GetVersion.

we'll need to add some details to the changelog / release eventually, but that'll be another PR.

Comment on lines +1586 to +1592
// customVersionOption forces GetVersion to return a specific version
type customVersionOption Version

func (c customVersionOption) apply(config *getVersionConfig) {
version := Version(c)
config.CustomVersion = &version
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yea. verbose and kinda annoying, but I really don't think we have much of an alternative ¯\_(ツ)_/¯

// GetVersion returns maxSupported version when is executed for the first time. This version is recorded into the
// workflow history as a marker event. Even if maxSupported version is changed the version that was recorded is
// returned on replay. DefaultVersion constant contains version of code that wasn't versioned before.
// Check documentation for ExecuteWithVersion and ExecuteWithMinVersion to make your changes forward and backward compatible.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yea, good enough for now anyway :) we can consider a bigger rewrite some other time.

@arzonus arzonus merged commit ebff2b1 into cadence-workflow:master Jun 30, 2025
11 checks passed
@arzonus arzonus deleted the add-options-to-get-version branch June 30, 2025 08:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants