Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ to docs, or any other relevant information.
`WorkflowOutboundCallsInterceptorBase` need to add them, and Base subclasses override them to see calls with options.

### Added
- Spring Boot now auto-configures `ActivityClient` for root and non-root namespaces, with namespace-specific
data converters, options customization, and root activity client interceptor registration.
- Applications can use the experimental `@TransferTypeConvertible` annotation and
`TransferTypeConverter` interface to convert top-level model values to transfer types. The SDK
applies this conversion to workflow, activity, Nexus, and schedule payloads, including failure
Expand Down
60 changes: 60 additions & 0 deletions temporal-spring-boot-autoconfigure/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,66 @@

For documentation on the Temporal Spring Boot Integration, please visit [https://docs.temporal.io/develop/java/spring-boot-integration](https://docs.temporal.io/develop/java/spring-boot-integration)

# Standalone Activity Client

Spring Boot auto-configures a primary `ActivityClient` bean named `temporalActivityClient` for the root namespace.
Each non-root namespace has a bean named `<alias-or-namespace>ActivityClient`, using its alias when configured,
otherwise its namespace. Each client uses its namespace's data converter and the same service stubs and connection
as its `ScheduleClient`.

For example, configure a connection and an additional namespace:

```yml
spring.temporal:
namespace: default
connection:
target: localhost:7233
namespaces:
- namespace: assign
alias: assign
```

Inject the root or a named non-root client and execute a standalone activity:

```java
import io.temporal.client.ActivityClient;
import io.temporal.client.StartActivityOptions;
import java.time.Duration;
import java.util.UUID;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

@Service
public class StandaloneActivities {
private final ActivityClient rootClient;
private final ActivityClient assignClient;

public StandaloneActivities(
ActivityClient rootClient,
@Qualifier("assignActivityClient") ActivityClient assignClient) {
this.rootClient = rootClient;
this.assignClient = assignClient;
}

public String greet(String name) {
StartActivityOptions options = StartActivityOptions.newBuilder()
.setId(UUID.randomUUID().toString())
.setTaskQueue("greetings")
.setStartToCloseTimeout(Duration.ofSeconds(30))
.build();
return rootClient.execute("Greet", String.class, options, name);
}
}
```

Use `TemporalOptionsCustomizer<ActivityClientOptions.Builder>` beans to customize the root client; multiple
customizers are applied in Spring order (for example, using `@Order`). For a non-root namespace, name its
customizer `<alias-or-namespace>ActivityClientCustomizer`, such as `assignActivityClientCustomizer`.
`ActivityClientInterceptor` beans are automatically registered only on the root client, just like schedule
client interceptors. Set non-root interceptors through that namespace's activity client options customizer.

Standalone activities are not supported by the test server.

# Running Multiple Name Space (experimental)

Along with the root namespace, you can configure multiple non-root namespaces in the application.yml file. Different namespaces can have different configurations including but not limited to different connection options, registered workflows/activities, data converters etc.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import com.google.common.base.MoreObjects;
import com.uber.m3.tally.Scope;
import io.opentracing.Tracer;
import io.temporal.client.ActivityClient;
import io.temporal.client.ActivityClientOptions;
import io.temporal.client.WorkflowClient;
import io.temporal.client.WorkflowClientOptions;
import io.temporal.client.WorkflowClientPlugin;
Expand Down Expand Up @@ -134,6 +136,8 @@ private void injectBeanByNonRootNamespace(NonRootNamespaceProperties ns) {
}
List<TemporalOptionsCustomizer<ScheduleClientOptions.Builder>> scheduleClientCustomizers =
findBeanByNameSpaceForTemporalCustomizer(beanPrefix, ScheduleClientOptions.Builder.class);
List<TemporalOptionsCustomizer<ActivityClientOptions.Builder>> activityClientCustomizers =
findBeanByNameSpaceForTemporalCustomizer(beanPrefix, ActivityClientOptions.Builder.class);
List<TemporalOptionsCustomizer<WorkflowImplementationOptions.Builder>>
workflowImplementationCustomizers =
findBeanByNameSpaceForTemporalCustomizer(
Expand Down Expand Up @@ -169,11 +173,14 @@ private void injectBeanByNonRootNamespace(NonRootNamespaceProperties ns) {
workflowImplementationCustomizers,
workflowClientPlugins,
scheduleClientPlugins,
workerPlugins);
workerPlugins,
null,
activityClientCustomizers);

ClientTemplate clientTemplate = namespaceTemplate.getClientTemplate();
WorkflowClient workflowClient = clientTemplate.getWorkflowClient();
ScheduleClient scheduleClient = clientTemplate.getScheduleClient();
ActivityClient activityClient = clientTemplate.getActivityClient();
WorkersTemplate workersTemplate = namespaceTemplate.getWorkersTemplate();
WorkerFactory workerFactory = workersTemplate.getWorkerFactory();

Expand All @@ -192,6 +199,8 @@ private void injectBeanByNonRootNamespace(NonRootNamespaceProperties ns) {
beanPrefix + WorkflowClient.class.getSimpleName(), workflowClient);
beanFactory.registerSingleton(
beanPrefix + ScheduleClient.class.getSimpleName(), scheduleClient);
beanFactory.registerSingleton(
beanPrefix + ActivityClient.class.getSimpleName(), activityClient);
beanFactory.registerSingleton(beanPrefix + WorkerFactory.class.getSimpleName(), workerFactory);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package io.temporal.spring.boot.autoconfigure;

import io.opentracing.Tracer;
import io.temporal.client.ActivityClient;
import io.temporal.client.ActivityClientOptions;
import io.temporal.client.WorkflowClient;
import io.temporal.client.WorkflowClientOptions;
import io.temporal.client.WorkflowClientPlugin;
import io.temporal.client.schedules.ScheduleClient;
import io.temporal.client.schedules.ScheduleClientOptions;
import io.temporal.client.schedules.ScheduleClientPlugin;
import io.temporal.common.converter.DataConverter;
import io.temporal.common.interceptors.ActivityClientInterceptor;
import io.temporal.common.interceptors.ScheduleClientInterceptor;
import io.temporal.common.interceptors.WorkerInterceptor;
import io.temporal.common.interceptors.WorkflowClientInterceptor;
Expand Down Expand Up @@ -75,6 +78,8 @@ public NamespaceTemplate rootNamespaceTemplate(
List<WorkflowClientInterceptor> workflowClientInterceptors,
@Autowired(required = false) @Nullable
List<ScheduleClientInterceptor> scheduleClientInterceptors,
@Autowired(required = false) @Nullable
List<ActivityClientInterceptor> activityClientInterceptors,
@Autowired(required = false) @Nullable List<WorkerInterceptor> workerInterceptors,
@Qualifier("temporalTestWorkflowEnvironmentAdapter") @Autowired(required = false) @Nullable
TestWorkflowEnvironmentAdapter testWorkflowEnvironment,
Expand All @@ -88,6 +93,9 @@ public NamespaceTemplate rootNamespaceTemplate(
@Autowired(required = false) @Nullable
Map<String, TemporalOptionsCustomizer<ScheduleClientOptions.Builder>>
scheduleCustomizerMap,
@Autowired(required = false) @Nullable
Map<String, TemporalOptionsCustomizer<ActivityClientOptions.Builder>>
activityCustomizerMap,
@Autowired(required = false) @Nullable
Map<String, TemporalOptionsCustomizer<WorkflowImplementationOptions.Builder>>
workflowImplementationCustomizerMap,
Expand Down Expand Up @@ -119,6 +127,9 @@ public NamespaceTemplate rootNamespaceTemplate(
List<TemporalOptionsCustomizer<ScheduleClientOptions.Builder>> scheduleCustomizer =
AutoConfigurationUtils.chooseTemporalCustomizerBeans(
beanFactory, scheduleCustomizerMap, ScheduleClientOptions.Builder.class, properties);
List<TemporalOptionsCustomizer<ActivityClientOptions.Builder>> activityCustomizer =
AutoConfigurationUtils.chooseTemporalCustomizerBeans(
beanFactory, activityCustomizerMap, ActivityClientOptions.Builder.class, properties);
List<TemporalOptionsCustomizer<WorkflowImplementationOptions.Builder>>
workflowImplementationCustomizer =
AutoConfigurationUtils.chooseTemporalCustomizerBeans(
Expand Down Expand Up @@ -166,7 +177,9 @@ public NamespaceTemplate rootNamespaceTemplate(
workflowImplementationCustomizer,
filteredClientPlugins,
filteredSchedulePlugins,
filteredWorkerPlugins);
filteredWorkerPlugins,
activityClientInterceptors,
activityCustomizer);
}

/** Client */
Expand All @@ -189,6 +202,12 @@ public ScheduleClient scheduleClient(ClientTemplate clientTemplate) {
return clientTemplate.getScheduleClient();
}

@Primary
@Bean(name = "temporalActivityClient")
public ActivityClient activityClient(ClientTemplate clientTemplate) {
return clientTemplate.getActivityClient();
}

/** Workers */
@Primary
@Bean(name = "temporalWorkersTemplate")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,9 @@ public TestWorkflowEnvironment testWorkflowEnvironment(
clientCustomizer,
scheduleCustomizer,
filteredClientPlugins,
filteredSchedulePlugins)
filteredSchedulePlugins,
null,
null)
.createWorkflowClientOptions());

if (metricsScope != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public class NonRootNamespaceProperties extends NamespaceProperties {
* - WorkersTemplate <br>
* - WorkflowClient <br>
* - ScheduleClient <br>
* - ActivityClient <br>
* - WorkerFactory <br>
* You guys can use this alias to get the beans. <br>
* for example if you set spring.temporal.namespace[0].alias=foo <br>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@

import com.google.common.base.Preconditions;
import io.opentracing.Tracer;
import io.temporal.client.ActivityClient;
import io.temporal.client.ActivityClientOptions;
import io.temporal.client.WorkflowClient;
import io.temporal.client.WorkflowClientOptions;
import io.temporal.client.WorkflowClientPlugin;
import io.temporal.client.schedules.ScheduleClient;
import io.temporal.client.schedules.ScheduleClientOptions;
import io.temporal.client.schedules.ScheduleClientPlugin;
import io.temporal.common.converter.DataConverter;
import io.temporal.common.interceptors.ActivityClientInterceptor;
import io.temporal.common.interceptors.ScheduleClientInterceptor;
import io.temporal.common.interceptors.WorkflowClientInterceptor;
import io.temporal.serviceclient.WorkflowServiceStubs;
Expand All @@ -25,6 +28,7 @@ public class ClientTemplate {

private WorkflowClient workflowClient;
private ScheduleClient scheduleClient;
private ActivityClient activityClient;

public ClientTemplate(
@Nonnull String namespace,
Expand All @@ -37,7 +41,10 @@ public ClientTemplate(
@Nullable List<TemporalOptionsCustomizer<WorkflowClientOptions.Builder>> clientCustomizers,
@Nullable List<TemporalOptionsCustomizer<ScheduleClientOptions.Builder>> scheduleCustomizers,
@Nullable List<WorkflowClientPlugin> workflowClientPlugins,
@Nullable List<ScheduleClientPlugin> scheduleClientPlugins) {
@Nullable List<ScheduleClientPlugin> scheduleClientPlugins,
@Nullable List<ActivityClientInterceptor> activityClientInterceptors,
@Nullable
List<TemporalOptionsCustomizer<ActivityClientOptions.Builder>> activityCustomizers) {
this.optionsTemplate =
new WorkflowClientOptionsTemplate(
namespace,
Expand All @@ -48,40 +55,13 @@ public ClientTemplate(
clientCustomizers,
scheduleCustomizers,
workflowClientPlugins,
scheduleClientPlugins);
scheduleClientPlugins,
activityClientInterceptors,
activityCustomizers);
this.workflowServiceStubs = workflowServiceStubs;
this.testWorkflowEnvironment = testWorkflowEnvironment;
}

/**
* @deprecated Use constructor with plugins parameters
*/
@Deprecated
public ClientTemplate(
@Nonnull String namespace,
@Nullable DataConverter dataConverter,
@Nullable List<WorkflowClientInterceptor> workflowClientInterceptors,
@Nullable List<ScheduleClientInterceptor> scheduleClientInterceptors,
@Nullable Tracer tracer,
@Nullable WorkflowServiceStubs workflowServiceStubs,
@Nullable TestWorkflowEnvironmentAdapter testWorkflowEnvironment,
@Nullable List<TemporalOptionsCustomizer<WorkflowClientOptions.Builder>> clientCustomizers,
@Nullable
List<TemporalOptionsCustomizer<ScheduleClientOptions.Builder>> scheduleCustomizers) {
this(
namespace,
dataConverter,
workflowClientInterceptors,
scheduleClientInterceptors,
tracer,
workflowServiceStubs,
testWorkflowEnvironment,
clientCustomizers,
scheduleCustomizers,
null,
null);
}

public WorkflowClient getWorkflowClient() {
if (workflowClient == null) {
this.workflowClient = createWorkflowClient();
Expand All @@ -96,6 +76,13 @@ public ScheduleClient getScheduleClient() {
return scheduleClient;
}

public ActivityClient getActivityClient() {
if (activityClient == null) {
this.activityClient = createActivityClient();
}
return activityClient;
}

private WorkflowClient createWorkflowClient() {
if (testWorkflowEnvironment != null) {
return testWorkflowEnvironment.getWorkflowClient();
Expand All @@ -118,4 +105,14 @@ private ScheduleClient createScheduleClient() {
workflowServiceStubs, optionsTemplate.createScheduleClientOptions());
}
}

private ActivityClient createActivityClient() {
WorkflowServiceStubs stubs = workflowServiceStubs;
if (testWorkflowEnvironment != null) {
stubs = testWorkflowEnvironment.getWorkflowClient().getWorkflowServiceStubs();
}
Preconditions.checkState(
stubs != null, "ClientTemplate was created without workflowServiceStubs");
return ActivityClient.newInstance(stubs, optionsTemplate.createActivityClientOptions());
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package io.temporal.spring.boot.autoconfigure.template;

import io.opentracing.Tracer;
import io.temporal.client.ActivityClientOptions;
import io.temporal.client.WorkflowClientOptions;
import io.temporal.client.WorkflowClientPlugin;
import io.temporal.client.schedules.ScheduleClientOptions;
import io.temporal.client.schedules.ScheduleClientPlugin;
import io.temporal.common.converter.DataConverter;
import io.temporal.common.interceptors.ActivityClientInterceptor;
import io.temporal.common.interceptors.ScheduleClientInterceptor;
import io.temporal.common.interceptors.WorkerInterceptor;
import io.temporal.common.interceptors.WorkflowClientInterceptor;
Expand Down Expand Up @@ -42,6 +44,9 @@ public class NamespaceTemplate {
private final @Nullable List<WorkflowClientPlugin> workflowClientPlugins;
private final @Nullable List<ScheduleClientPlugin> scheduleClientPlugins;
private final @Nullable List<WorkerPlugin> workerPlugins;
private final @Nullable List<ActivityClientInterceptor> activityClientInterceptors;
private final @Nullable List<TemporalOptionsCustomizer<ActivityClientOptions.Builder>>
activityCustomizers;

private ClientTemplate clientTemplate;
private WorkersTemplate workersTemplate;
Expand All @@ -65,7 +70,10 @@ public NamespaceTemplate(
workflowImplementationCustomizers,
@Nullable List<WorkflowClientPlugin> workflowClientPlugins,
@Nullable List<ScheduleClientPlugin> scheduleClientPlugins,
@Nullable List<WorkerPlugin> workerPlugins) {
@Nullable List<WorkerPlugin> workerPlugins,
@Nullable List<ActivityClientInterceptor> activityClientInterceptors,
@Nullable
List<TemporalOptionsCustomizer<ActivityClientOptions.Builder>> activityCustomizers) {
this.namespaceProperties = namespaceProperties;
this.workflowServiceStubs = workflowServiceStubs;
this.dataConverter = dataConverter;
Expand All @@ -83,6 +91,8 @@ public NamespaceTemplate(
this.workflowClientPlugins = workflowClientPlugins;
this.scheduleClientPlugins = scheduleClientPlugins;
this.workerPlugins = workerPlugins;
this.activityClientInterceptors = activityClientInterceptors;
this.activityCustomizers = activityCustomizers;
}

public ClientTemplate getClientTemplate() {
Expand All @@ -99,7 +109,9 @@ public ClientTemplate getClientTemplate() {
clientCustomizers,
scheduleCustomizers,
workflowClientPlugins,
scheduleClientPlugins);
scheduleClientPlugins,
activityClientInterceptors,
activityCustomizers);
}
return clientTemplate;
}
Expand Down
Loading
Loading