Skip to content
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

Add initial Spring Boot implementation #1305

Merged
merged 1 commit into from
Jul 14, 2022
Merged
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: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ target
.gradle
/build
/*/build
/out
**/out
/lib
dummy
$buildDir
Expand Down
13 changes: 8 additions & 5 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,23 @@ ext {
// We have to use specific versions with platforms until Maven 4.0.0 is released
// See https://github.com/temporalio/sdk-java/issues/1033
//
// grpcVersion = '[1.34.0,)!!1.44.1'
// grpcVersion = '[1.34.0,)!!1.47.0'
// jacksonVersion = '[2.9.0,)!!2.13.1'
// micrometerVersion = '[1.0.0,)!!1.8.3'
// micrometerVersion = '[1.0.0,)!!1.9.2'
// springBootVersion = '[2.4.0,)!!2.7.1'
grpcVersion = '1.47.0'
jacksonVersion = '2.13.1'
micrometerVersion = '1.9.1'
micrometerVersion = '1.9.2'
springBootVersion = '2.7.1'

slf4jVersion = '[1.4.0,)!!1.7.36'
protoVersion = '[3.10.0,3.99)!!3.20.1'
annotationApiVersion = '1.3.2'
guavaVersion = '[10.0,)!!31.1-jre'
jsonPathVersion = '2.7.0'
tallyVersion = '[0.4.0,)!!0.11.1'

jsonPathVersion = '2.7.0'
gsonVersion = '[2.0,)!!2.9.0'
slf4jVersion = '[1.4.0,)!!1.7.36'

logbackVersion = '1.2.11'
mockitoVersion = '4.6.1'
Expand Down
2 changes: 2 additions & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ include 'temporal-testing'
include 'temporal-test-server'
include 'temporal-opentracing'
include 'temporal-kotlin'
include 'temporal-spring-boot-autoconfigure-alpha'
include 'temporal-spring-boot-starter-alpha'
4 changes: 1 addition & 3 deletions temporal-sdk/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@ dependencies {
api "com.fasterxml.jackson.core:jackson-databind"
implementation "com.fasterxml.jackson.datatype:jackson-datatype-jsr310"
implementation "com.fasterxml.jackson.datatype:jackson-datatype-jdk8"
if (!JavaVersion.current().isJava8()) {
implementation 'javax.annotation:javax.annotation-api:1.3.2'
}

// compileOnly and testImplementation because this dependency is needed only to work with json format of history
// which shouldn't be needed for any production usage of temporal-sdk.
// It's useful only for unit tests and debugging.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@
* machines in case of child workflow task execution failure and contains an original unparsed
* Failure message with details from the attributes in the exception.
*
* <p>This class is needed to don't make Failure -> Exception conversion inside the state machines.
* So the state machine forms ChildWorkflowFailure without cause and parse the original Failure, so
* the outside code may join them together.
* <p>This class is needed to don't make Failure -&gt; Exception conversion inside the state
* machines. So the state machine forms ChildWorkflowFailure without cause and parse the original
* Failure, so the outside code may join them together.
*/
public class ChildWorkflowTaskFailedException extends RuntimeException {

Expand Down
4 changes: 2 additions & 2 deletions temporal-serviceclient/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ dependencies {
api "io.grpc:grpc-stub" //Part of WorkflowServiceStubs API
api "io.grpc:grpc-netty-shaded" //Part of WorkflowServiceStubs API, specifically SslContext
api "com.google.protobuf:protobuf-java-util:$protoVersion" //proto request and response objects are a part of this module's API
if (!JavaVersion.current().isJava8()) {
//needed for the generated grpc stubs
if (JavaVersion.current().isJava9Compatible()) {
//needed for the generated grpc stubs and is not a part of JDK since java 9
implementation "javax.annotation:javax.annotation-api:$annotationApiVersion"
}

Expand Down
30 changes: 30 additions & 0 deletions temporal-spring-boot-autoconfigure-alpha/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
description = '''Spring Boot AutoConfigure for Temporal Java SDK'''

dependencies {
api(platform("org.springframework.boot:spring-boot-dependencies:$springBootVersion"))

api project(':temporal-sdk')
compileOnly project(':temporal-testing')

implementation "org.springframework.boot:spring-boot"
implementation "org.springframework.boot:spring-boot-autoconfigure"

// this dependency is not mandatory for the module to work, but it provides a better IDE experience developing the module
// it's needed to auto-generate configuration metadata. See for more details:
// https://docs.spring.io/spring-boot/docs/2.4.0/reference/html/appendix-configuration-metadata.html#configuration-metadata-annotation-processor
annotationProcessor "org.springframework.boot:spring-boot-configuration-processor:$springBootVersion"

testImplementation project(':temporal-testing')

testImplementation "org.mockito:mockito-core:${mockitoVersion}"
testImplementation group: 'ch.qos.logback', name: 'logback-classic', version: "${logbackVersion}"

testImplementation "org.springframework.boot:spring-boot-starter-test"
}

tasks.test {
useJUnitPlatform()
testLogging {
events("passed", "skipped", "failed")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved.
*
* Copyright (C) 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Modifications copyright (C) 2017 Uber Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this material except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.temporal.spring.boot;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface ActivityImpl {
String[] taskQueues();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved.
*
* Copyright (C) 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Modifications copyright (C) 2017 Uber Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this material except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.temporal.spring.boot;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface WorkflowImpl {
String[] taskQueues();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved.
*
* Copyright (C) 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Modifications copyright (C) 2017 Uber Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this material except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.temporal.spring.boot.autoconfigure;

import io.temporal.client.WorkflowClient;
import io.temporal.client.WorkflowClientOptions;
import io.temporal.serviceclient.WorkflowServiceStubs;
import io.temporal.serviceclient.WorkflowServiceStubsOptions;
import io.temporal.spring.boot.autoconfigure.properties.ClientProperties;
import io.temporal.spring.boot.autoconfigure.properties.TemporalProperties;
import io.temporal.testing.TestWorkflowEnvironment;
import javax.annotation.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.support.BeanDefinitionValidationException;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/** Provides a client based on `spring.temporal.client` section */
@Configuration
@EnableConfigurationProperties(TemporalProperties.class)
@ConditionalOnClass(name = "io.temporal.client.WorkflowClient")
@ConditionalOnProperty(prefix = "spring.temporal", name = "client.target")
public class ClientAutoConfiguration {

@Bean(name = "temporalWorkflowClient")
@ConditionalOnMissingBean(name = "temporalWorkflowClient")
public WorkflowClient client(
TemporalProperties temporalProperties,
@Qualifier("temporalTestWorkflowEnvironment") @Autowired(required = false) @Nullable
TestWorkflowEnvironment testWorkflowEnvironment) {
ClientProperties clientProperties = temporalProperties.getClient();
WorkflowServiceStubs workflowServiceStubs;
switch (temporalProperties.getClient().getTarget().toLowerCase()) {
case ClientProperties.TARGET_IN_PROCESS_TEST_SERVER:
if (testWorkflowEnvironment == null) {
throw new BeanDefinitionValidationException(
"Test workflow environment is not available. "
+ "Please make sure that you have enabled the 'temporal-test' module "
+ "and configured `spring.temporal.testServer.enabled: true`.");
}
return testWorkflowEnvironment.getWorkflowClient();
case ClientProperties.TARGET_LOCAL_SERVICE:
Copy link
Member

@cretz cretz Jul 14, 2022

Choose a reason for hiding this comment

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

What is the difference between this and me explicitly providing localhost:7233? Can the const just be that and we don't have a case for it? Or is it that important to reuse the stub instance?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There is none, but we have WorkflowServerStub.newLocalStubs, so I think it would be right to mirror it here.
The main argument for this is that it's much friendlier for new users to don't even worry about which port Temporal occupies by default.

workflowServiceStubs = WorkflowServiceStubs.newLocalServiceStubs();
break;
default:
WorkflowServiceStubsOptions.Builder stubsOptionsBuilder =
WorkflowServiceStubsOptions.newBuilder();

if (clientProperties.getTarget() != null) {
stubsOptionsBuilder.setTarget(clientProperties.getTarget());
}

workflowServiceStubs =
WorkflowServiceStubs.newServiceStubs(
stubsOptionsBuilder.validateAndBuildWithDefaults());
}

WorkflowClientOptions.Builder clientOptionsBuilder = WorkflowClientOptions.newBuilder();
Copy link
Member

Choose a reason for hiding this comment

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

I assume configure for other options can come later? E.g. can I "inject" (or whatever spring calls it these days) an interceptor or data converter?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

"More configuration and deep integration with Spring DI will be coming in subsequent PRs for the Spring Boot story."

The purpose of this PR is to provide a correct structure that will initialize things in the correct order and with reasonable logic. You can't configure almost anything not absolutely essential here yet.


if (clientProperties.getNamespace() != null) {
clientOptionsBuilder.setNamespace(clientProperties.getNamespace());
}

return WorkflowClient.newInstance(
workflowServiceStubs, clientOptionsBuilder.validateAndBuildWithDefaults());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved.
*
* Copyright (C) 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Modifications copyright (C) 2017 Uber Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this material except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.temporal.spring.boot.autoconfigure;

import io.temporal.spring.boot.autoconfigure.properties.TemporalProperties;
import io.temporal.testing.TestWorkflowEnvironment;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/** Provides a client based on `spring.temporal.testServer` section */
@Configuration
@EnableConfigurationProperties(TemporalProperties.class)
@ConditionalOnClass(name = "io.temporal.testing.TestWorkflowEnvironment")
@ConditionalOnProperty(
prefix = "spring.temporal",
name = "testServer.enabled",
havingValue = "true")
public class TestServerAutoConfiguration {
@Bean(name = "temporalTestWorkflowEnvironment", destroyMethod = "close")
public TestWorkflowEnvironment testServer() {
return TestWorkflowEnvironment.newInstance();
}
}
Loading