Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ target
.project
.settings/
bin/
core/.vscode/
core/.vscode/
.claude/
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,9 @@ See the README.md file in each main sample directory for cut/paste Gradle comman

- [**Custom Annotation**](/core/src/main/java/io/temporal/samples/customannotation): Demonstrates how to create a custom annotation using an interceptor.

- [**Asnyc Packet Delivery**](/core/src/main/java/io/temporal/samples/packetdelivery): Demonstrates running multiple execution paths async within single execution.
- [**Async Packet Delivery**](/core/src/main/java/io/temporal/samples/packetdelivery): Demonstrates running multiple execution paths async within single execution.

- [**Worker Versioning**](/core/src/main/java/io/temporal/samples/workerversioning): Demonstrates how to use worker versioning to manage workflow code changes.

#### API demonstrations

Expand Down
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) {
Copy link
Contributor

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

Copy link
Member Author

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

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).
Copy link
Contributor

Choose a reason for hiding this comment

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

What happens if the user is on an old version of Temporal that doesn't support worker versioning or it isn't enabled? Will this sample fail with a clear error they need to enable it?

Copy link
Member Author

Choose a reason for hiding this comment

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

It's enabled by default at this point, so the main thing is just to make sure they have a recent version. I've added that to the readme.


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.

Loading
Loading