-
Notifications
You must be signed in to change notification settings - Fork 170
Create worker deployment based versioning sample #754
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,4 +17,5 @@ target | |
| .project | ||
| .settings/ | ||
| bin/ | ||
| core/.vscode/ | ||
| core/.vscode/ | ||
| .claude/ | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| package io.temporal.samples.workerversioning; | ||
|
|
||
| import io.temporal.activity.ActivityInterface; | ||
| import io.temporal.activity.ActivityMethod; | ||
|
|
||
| @ActivityInterface | ||
| public interface Activities { | ||
|
|
||
| @ActivityMethod | ||
| String someActivity(String calledBy); | ||
|
|
||
| @ActivityMethod | ||
| String someIncompatibleActivity(IncompatibleActivityInput input); | ||
|
|
||
| class IncompatibleActivityInput { | ||
| String calledBy; | ||
| String moreData; | ||
|
|
||
| public IncompatibleActivityInput() {} | ||
|
|
||
| public IncompatibleActivityInput(String calledBy, String moreData) { | ||
| this.calledBy = calledBy; | ||
| this.moreData = moreData; | ||
| } | ||
|
|
||
| public String getCalledBy() { | ||
| return calledBy; | ||
| } | ||
|
|
||
| public String getMoreData() { | ||
| return moreData; | ||
| } | ||
|
|
||
| public void setCalledBy(String calledBy) { | ||
| this.calledBy = calledBy; | ||
| } | ||
|
|
||
| public void setMoreData(String moreData) { | ||
| this.moreData = moreData; | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| package io.temporal.samples.workerversioning; | ||
|
|
||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
|
|
||
| public class ActivitiesImpl implements Activities { | ||
|
|
||
| private static final Logger logger = LoggerFactory.getLogger(ActivitiesImpl.class); | ||
|
|
||
| @Override | ||
| public String someActivity(String calledBy) { | ||
| logger.info("SomeActivity called by {}", calledBy); | ||
| return "SomeActivity called by " + calledBy; | ||
| } | ||
|
|
||
| @Override | ||
| public String someIncompatibleActivity(IncompatibleActivityInput input) { | ||
| logger.info( | ||
| "SomeIncompatibleActivity called by {} with {}", input.getCalledBy(), input.getMoreData()); | ||
| return "SomeIncompatibleActivity called by " | ||
| + input.getCalledBy() | ||
| + " with " | ||
| + input.getMoreData(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package io.temporal.samples.workerversioning; | ||
|
|
||
| import io.temporal.workflow.SignalMethod; | ||
| import io.temporal.workflow.WorkflowInterface; | ||
| import io.temporal.workflow.WorkflowMethod; | ||
|
|
||
| @WorkflowInterface | ||
| public interface AutoUpgradingWorkflow { | ||
|
|
||
| @WorkflowMethod | ||
| void run(); | ||
|
|
||
| @SignalMethod | ||
| void doNextSignal(String signal); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| package io.temporal.samples.workerversioning; | ||
|
|
||
| import io.temporal.activity.ActivityOptions; | ||
| import io.temporal.common.VersioningBehavior; | ||
| import io.temporal.workflow.Workflow; | ||
| import io.temporal.workflow.WorkflowVersioningBehavior; | ||
| import java.time.Duration; | ||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
| import org.slf4j.Logger; | ||
|
|
||
| /** | ||
| * This workflow will automatically move to the latest worker version. We'll be making changes to | ||
| * it, which must be replay safe. Note that generally you won't want or need to include a version | ||
| * number in your workflow name if you're using the worker versioning feature. This sample does it | ||
| * to illustrate changes to the same code over time - but really what we're demonstrating here is | ||
| * the evolution of what would have been one workflow definition. | ||
| */ | ||
| public class AutoUpgradingWorkflowV1Impl implements AutoUpgradingWorkflow { | ||
|
|
||
| private static final Logger logger = Workflow.getLogger(AutoUpgradingWorkflowV1Impl.class); | ||
|
|
||
| private final List<String> signals = new ArrayList<>(); | ||
| private final Activities activities = | ||
| Workflow.newActivityStub( | ||
| Activities.class, | ||
| ActivityOptions.newBuilder().setStartToCloseTimeout(Duration.ofSeconds(10)).build()); | ||
|
|
||
| @Override | ||
| @WorkflowVersioningBehavior(VersioningBehavior.AUTO_UPGRADE) | ||
| public void run() { | ||
| logger.info("Changing workflow v1 started. StartTime: {}", Workflow.currentTimeMillis()); | ||
|
|
||
| while (true) { | ||
| Workflow.await(() -> !signals.isEmpty()); | ||
| String signal = signals.remove(0); | ||
|
|
||
| if ("do-activity".equals(signal)) { | ||
| logger.info("Changing workflow v1 running activity"); | ||
| activities.someActivity("v1"); | ||
| } else { | ||
| logger.info("Concluding workflow v1"); | ||
| return; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public void doNextSignal(String signal) { | ||
| signals.add(signal); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| package io.temporal.samples.workerversioning; | ||
|
|
||
| import io.temporal.activity.ActivityOptions; | ||
| import io.temporal.common.VersioningBehavior; | ||
| import io.temporal.workflow.Workflow; | ||
| import io.temporal.workflow.WorkflowVersioningBehavior; | ||
| import java.time.Duration; | ||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
| import org.slf4j.Logger; | ||
|
|
||
| /** | ||
| * This represents us having made *compatible* changes to AutoUpgradingWorkflowV1Impl. | ||
| * | ||
| * <p>The compatible changes we've made are: | ||
| * | ||
| * <ul> | ||
| * <li>Altering the log lines | ||
| * <li>Using the `Workflow.getVersion` API to properly introduce branching behavior while | ||
| * maintaining compatibility | ||
| * </ul> | ||
| */ | ||
| public class AutoUpgradingWorkflowV1bImpl implements AutoUpgradingWorkflow { | ||
|
|
||
| private static final Logger logger = Workflow.getLogger(AutoUpgradingWorkflowV1bImpl.class); | ||
|
|
||
| private final List<String> signals = new ArrayList<>(); | ||
| private final Activities activities = | ||
| Workflow.newActivityStub( | ||
| Activities.class, | ||
| ActivityOptions.newBuilder().setStartToCloseTimeout(Duration.ofSeconds(10)).build()); | ||
|
|
||
| @Override | ||
| @WorkflowVersioningBehavior(VersioningBehavior.AUTO_UPGRADE) | ||
| public void run() { | ||
| logger.info("Changing workflow v1b started. StartTime: {}", Workflow.currentTimeMillis()); | ||
|
|
||
| while (true) { | ||
| Workflow.await(() -> !signals.isEmpty()); | ||
| String signal = signals.remove(0); | ||
|
|
||
| if ("do-activity".equals(signal)) { | ||
| logger.info("Changing workflow v1b running activity"); | ||
| int version = Workflow.getVersion("DifferentActivity", Workflow.DEFAULT_VERSION, 1); | ||
| if (version == 1) { | ||
| activities.someIncompatibleActivity( | ||
| new Activities.IncompatibleActivityInput("v1b", "hello!")); | ||
| } else { | ||
| // Note it is a valid compatible change to alter the input to an activity. | ||
| // However, because we're using the getVersion API, this branch will never be | ||
| // taken. | ||
| activities.someActivity("v1b"); | ||
| } | ||
| } else { | ||
| logger.info("Concluding workflow v1b"); | ||
| break; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public void doNextSignal(String signal) { | ||
| signals.add(signal); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package io.temporal.samples.workerversioning; | ||
|
|
||
| import io.temporal.workflow.SignalMethod; | ||
| import io.temporal.workflow.WorkflowInterface; | ||
| import io.temporal.workflow.WorkflowMethod; | ||
|
|
||
| @WorkflowInterface | ||
| public interface PinnedWorkflow { | ||
|
|
||
| @WorkflowMethod | ||
| void run(); | ||
|
|
||
| @SignalMethod | ||
| void doNextSignal(String signal); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| package io.temporal.samples.workerversioning; | ||
|
|
||
| import io.temporal.activity.ActivityOptions; | ||
| import io.temporal.common.VersioningBehavior; | ||
| import io.temporal.workflow.Workflow; | ||
| import io.temporal.workflow.WorkflowVersioningBehavior; | ||
| import java.time.Duration; | ||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
| import org.slf4j.Logger; | ||
|
|
||
| /** | ||
| * This workflow represents one that likely has a short lifetime, and we want to always stay pinned | ||
| * to the same version it began on. Note that generally you won't want or need to include a version | ||
| * number in your workflow name if you're using the worker versioning feature. This sample does it | ||
| * to illustrate changes to the same code over time - but really what we're demonstrating here is | ||
| * the evolution of what would have been one workflow definition. | ||
| */ | ||
| public class PinnedWorkflowV1Impl implements PinnedWorkflow { | ||
|
|
||
| private static final Logger logger = Workflow.getLogger(PinnedWorkflowV1Impl.class); | ||
|
|
||
| private final List<String> signals = new ArrayList<>(); | ||
| private final Activities activities = | ||
| Workflow.newActivityStub( | ||
| Activities.class, | ||
| ActivityOptions.newBuilder().setStartToCloseTimeout(Duration.ofSeconds(10)).build()); | ||
|
|
||
| @Override | ||
| @WorkflowVersioningBehavior(VersioningBehavior.PINNED) | ||
| public void run() { | ||
| logger.info("Pinned Workflow v1 started. StartTime: {}", Workflow.currentTimeMillis()); | ||
|
|
||
| while (true) { | ||
| Workflow.await(() -> !signals.isEmpty()); | ||
| String signal = signals.remove(0); | ||
| if ("conclude".equals(signal)) { | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| activities.someActivity("Pinned-v1"); | ||
| } | ||
|
|
||
| @Override | ||
| public void doNextSignal(String signal) { | ||
| signals.add(signal); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| package io.temporal.samples.workerversioning; | ||
|
|
||
| import io.temporal.activity.ActivityOptions; | ||
| import io.temporal.common.VersioningBehavior; | ||
| import io.temporal.workflow.Workflow; | ||
| import io.temporal.workflow.WorkflowVersioningBehavior; | ||
| import java.time.Duration; | ||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
| import org.slf4j.Logger; | ||
|
|
||
| /** | ||
| * This workflow has changes that would make it incompatible with v1, and aren't protected by a | ||
| * patch. | ||
| */ | ||
| public class PinnedWorkflowV2Impl implements PinnedWorkflow { | ||
|
|
||
| private static final Logger logger = Workflow.getLogger(PinnedWorkflowV2Impl.class); | ||
|
|
||
| private final List<String> signals = new ArrayList<>(); | ||
| private final Activities activities = | ||
| Workflow.newActivityStub( | ||
| Activities.class, | ||
| ActivityOptions.newBuilder().setStartToCloseTimeout(Duration.ofSeconds(10)).build()); | ||
|
|
||
| @Override | ||
| @WorkflowVersioningBehavior(VersioningBehavior.PINNED) | ||
| public void run() { | ||
| logger.info("Pinned Workflow v2 started. StartTime: {}", Workflow.currentTimeMillis()); | ||
|
|
||
| // Here we call an activity where we didn't before, which is an incompatible change. | ||
| activities.someActivity("Pinned-v2"); | ||
|
|
||
| while (true) { | ||
| Workflow.await(() -> !signals.isEmpty()); | ||
| String signal = signals.remove(0); | ||
| if ("conclude".equals(signal)) { | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| // We've also changed the activity type here, another incompatible change | ||
| activities.someIncompatibleActivity( | ||
| new Activities.IncompatibleActivityInput("Pinned-v2", "hi")); | ||
| } | ||
|
|
||
| @Override | ||
| public void doNextSignal(String signal) { | ||
| signals.add(signal); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| # Worker Versioning | ||
|
|
||
| This sample demonstrates how to use Temporal's Worker Versioning feature to safely deploy updates to workflow and activity code. It shows the difference between auto-upgrading and pinned workflows, and how to manage worker deployments with different build IDs. | ||
|
|
||
| The sample creates multiple worker versions (1.0, 1.1, and 2.0) within one deployment and demonstrates: | ||
| - **Auto-upgrading workflows**: Automatically and controllably migrate to newer worker versions | ||
| - **Pinned workflows**: Stay on the original worker version throughout their lifecycle | ||
| - **Compatible vs incompatible changes**: How to make safe updates using `Workflow.getVersion` | ||
|
|
||
| ## Steps to run this sample: | ||
|
|
||
| 1) Run a [Temporal service](https://github.com/temporalio/samples-java/tree/main/#how-to-use). | ||
|
||
|
|
||
| 2) Start the main application (this will guide you through the sample): | ||
| ```bash | ||
| ./gradlew -q execute -PmainClass=io.temporal.samples.workerversioning.Starter | ||
| ``` | ||
| 3) Follow the prompts to start workers in separate terminals: | ||
| - When prompted, run: `./gradlew -q execute -PmainClass=io.temporal.samples.workerversioning.WorkerV1` | ||
| - When prompted, run: `./gradlew -q execute -PmainClass=io.temporal.samples.workerversioning.WorkerV1_1` | ||
| - When prompted, run: `./gradlew -q execute -PmainClass=io.temporal.samples.workerversioning.WorkerV2` | ||
|
|
||
| Follow the prompts in the example to observe auto-upgrading workflows migrating to newer workers | ||
| while pinned workflows remain on their original versions. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not how I would personally recommend a user create an input type, I would recommend annotating the constructor
https://github.com/temporalio/samples-java/blob/main/core/src/main/java/io/temporal/samples/nexus/service/NexusService.java#L18
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was looking for something like that and couldn't find it. Thanks