From 69f24075f603d42b4ae6f11d7711a86706148641 Mon Sep 17 00:00:00 2001 From: salaboy Date: Fri, 3 Jan 2025 08:30:42 +0100 Subject: [PATCH] Workflows alignment with Spring Boot (Dependency Injection for Workflows and Activities) (#1170) * evaluating annotations Signed-off-by: salaboy * adding workflows autoconfig Signed-off-by: salaboy * initial @EnableDaprWorkflows implementation Signed-off-by: salaboy * validating managed beans for workflows and activities Signed-off-by: salaboy * [docs] remove 'beta' for Java SDK workflow docs (#1169) * fix link Signed-off-by: Hannah Hunter * fix one more link Signed-off-by: Hannah Hunter * remove beta from Java SDK workflow Signed-off-by: Hannah Hunter --------- Signed-off-by: Hannah Hunter Signed-off-by: salaboy * Fix create release to run with right JDK (#1171) Signed-off-by: Artur Souza Signed-off-by: salaboy * Remove test from create release script. (#1172) Signed-off-by: Artur Souza Signed-off-by: salaboy * Fix typo crashing release script. (#1173) Signed-off-by: Artur Souza Signed-off-by: salaboy * Update master version to 1.14.0-SNAPSHOT (#1174) Signed-off-by: Dapr Bot Signed-off-by: salaboy * Generate updated javadocs for 1.13.1 (#1179) Signed-off-by: Dapr Bot Co-authored-by: Dapr Bot Signed-off-by: salaboy * upgrading snap Signed-off-by: salaboy * fix checkstyle Signed-off-by: salaboy * Fix conflict and build trigger on right branch for SNAPSHOT. (#1180) Signed-off-by: Artur Souza Signed-off-by: salaboy * private internals Signed-off-by: salaboy --------- Signed-off-by: salaboy Signed-off-by: Hannah Hunter Signed-off-by: Artur Souza Signed-off-by: Dapr Bot Co-authored-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Co-authored-by: Artur Souza Co-authored-by: Dapr Bot <56698301+dapr-bot@users.noreply.github.com> Co-authored-by: Dapr Bot --- .../dapr-spring-boot-autoconfigure/pom.xml | 6 ++ .../client/DaprClientAutoConfiguration.java | 30 ++++++++++ .../DaprWorkflowsRegistrationTests.java | 44 +++++++++++++++ .../client/WorkflowTestApplication.java | 24 ++++++++ .../client/workflows/TestActivity.java | 21 +++++++ .../client/workflows/TestWorkflow.java | 23 ++++++++ .../dapr-spring-boot-starter/pom.xml | 5 ++ dapr-spring/dapr-spring-workflows/pom.xml | 24 ++++++++ .../config/DaprWorkflowsConfiguration.java | 55 +++++++++++++++++++ .../workflows/config/EnableDaprWorkflows.java | 16 ++++++ dapr-spring/pom.xml | 1 + 11 files changed, 249 insertions(+) create mode 100644 dapr-spring/dapr-spring-boot-autoconfigure/src/test/java/io/dapr/spring/boot/autoconfigure/client/DaprWorkflowsRegistrationTests.java create mode 100644 dapr-spring/dapr-spring-boot-autoconfigure/src/test/java/io/dapr/spring/boot/autoconfigure/client/WorkflowTestApplication.java create mode 100644 dapr-spring/dapr-spring-boot-autoconfigure/src/test/java/io/dapr/spring/boot/autoconfigure/client/workflows/TestActivity.java create mode 100644 dapr-spring/dapr-spring-boot-autoconfigure/src/test/java/io/dapr/spring/boot/autoconfigure/client/workflows/TestWorkflow.java create mode 100644 dapr-spring/dapr-spring-workflows/pom.xml create mode 100644 dapr-spring/dapr-spring-workflows/src/main/java/io/dapr/spring/workflows/config/DaprWorkflowsConfiguration.java create mode 100644 dapr-spring/dapr-spring-workflows/src/main/java/io/dapr/spring/workflows/config/EnableDaprWorkflows.java diff --git a/dapr-spring/dapr-spring-boot-autoconfigure/pom.xml b/dapr-spring/dapr-spring-boot-autoconfigure/pom.xml index 3ef3eecbd..16df49076 100644 --- a/dapr-spring/dapr-spring-boot-autoconfigure/pom.xml +++ b/dapr-spring/dapr-spring-boot-autoconfigure/pom.xml @@ -27,6 +27,12 @@ ${project.parent.version} true + + io.dapr.spring + dapr-spring-workflows + ${project.parent.version} + true + org.springframework.boot spring-boot-starter diff --git a/dapr-spring/dapr-spring-boot-autoconfigure/src/main/java/io/dapr/spring/boot/autoconfigure/client/DaprClientAutoConfiguration.java b/dapr-spring/dapr-spring-boot-autoconfigure/src/main/java/io/dapr/spring/boot/autoconfigure/client/DaprClientAutoConfiguration.java index f77683424..7e10c1f8f 100644 --- a/dapr-spring/dapr-spring-boot-autoconfigure/src/main/java/io/dapr/spring/boot/autoconfigure/client/DaprClientAutoConfiguration.java +++ b/dapr-spring/dapr-spring-boot-autoconfigure/src/main/java/io/dapr/spring/boot/autoconfigure/client/DaprClientAutoConfiguration.java @@ -16,12 +16,17 @@ import io.dapr.client.DaprClient; import io.dapr.client.DaprClientBuilder; import io.dapr.config.Properties; +import io.dapr.workflows.client.DaprWorkflowClient; +import io.dapr.workflows.runtime.WorkflowRuntimeBuilder; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; +import java.util.HashMap; +import java.util.Map; + @AutoConfiguration @ConditionalOnClass(DaprClient.class) @EnableConfigurationProperties(DaprClientProperties.class) @@ -58,4 +63,29 @@ DaprClient daprClient(DaprClientBuilder daprClientBuilder) { return daprClientBuilder.build(); } + @Bean + @ConditionalOnMissingBean + DaprWorkflowClient daprWorkflowClient(DaprConnectionDetails daprConnectionDetails) { + Properties properties = createPropertiesFromConnectionDetails(daprConnectionDetails); + return new DaprWorkflowClient(properties); + } + + @Bean + @ConditionalOnMissingBean + WorkflowRuntimeBuilder daprWorkflowRuntimeBuilder(DaprConnectionDetails daprConnectionDetails) { + Properties properties = createPropertiesFromConnectionDetails(daprConnectionDetails); + return new WorkflowRuntimeBuilder(properties); + } + + private Properties createPropertiesFromConnectionDetails(DaprConnectionDetails daprConnectionDetails) { + final Map propertyOverrides = new HashMap<>(); + propertyOverrides.put(Properties.HTTP_ENDPOINT.getName(), daprConnectionDetails.httpEndpoint()); + propertyOverrides.put(Properties.HTTP_PORT.getName(), String.valueOf(daprConnectionDetails.httpPort())); + propertyOverrides.put(Properties.GRPC_ENDPOINT.getName(), daprConnectionDetails.grpcEndpoint()); + propertyOverrides.put(Properties.GRPC_PORT.getName(), String.valueOf(daprConnectionDetails.grpcPort())); + return new Properties(propertyOverrides); + } + + + } diff --git a/dapr-spring/dapr-spring-boot-autoconfigure/src/test/java/io/dapr/spring/boot/autoconfigure/client/DaprWorkflowsRegistrationTests.java b/dapr-spring/dapr-spring-boot-autoconfigure/src/test/java/io/dapr/spring/boot/autoconfigure/client/DaprWorkflowsRegistrationTests.java new file mode 100644 index 000000000..78a99cc34 --- /dev/null +++ b/dapr-spring/dapr-spring-boot-autoconfigure/src/test/java/io/dapr/spring/boot/autoconfigure/client/DaprWorkflowsRegistrationTests.java @@ -0,0 +1,44 @@ +package io.dapr.spring.boot.autoconfigure.client; + +import io.dapr.spring.boot.autoconfigure.client.workflows.TestActivity; +import io.dapr.spring.boot.autoconfigure.client.workflows.TestWorkflow; +import io.dapr.workflows.client.DaprWorkflowClient; +import io.dapr.workflows.runtime.WorkflowRuntimeBuilder; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@SpringBootTest(classes = {WorkflowTestApplication.class, DaprClientAutoConfiguration.class, TestActivity.class, TestWorkflow.class}) +public class DaprWorkflowsRegistrationTests { + + @Autowired + private DaprWorkflowClient daprWorkflowClient; + + @Autowired + private WorkflowRuntimeBuilder workflowRuntimeBuilder; + + @Autowired + private TestActivity testActivity; + + @Autowired + private TestWorkflow testWorkflow; + + @Test + public void testWorkflowInjection(){ + + //I cannot test here if the client works, as it needs the runtime + assertNotNull(daprWorkflowClient); + + //@TODO: there is no way to assert the runtime and its registered workflows and activities + assertNotNull(workflowRuntimeBuilder); + + //Check that both Activities and Workflows are managed beans + assertNotNull(testActivity); + assertNotNull(testWorkflow); + assertNotNull(testActivity.getRestTemplate()); + assertNotNull(testWorkflow.getRestTemplate()); + + } +} diff --git a/dapr-spring/dapr-spring-boot-autoconfigure/src/test/java/io/dapr/spring/boot/autoconfigure/client/WorkflowTestApplication.java b/dapr-spring/dapr-spring-boot-autoconfigure/src/test/java/io/dapr/spring/boot/autoconfigure/client/WorkflowTestApplication.java new file mode 100644 index 000000000..2f9fc27f4 --- /dev/null +++ b/dapr-spring/dapr-spring-boot-autoconfigure/src/test/java/io/dapr/spring/boot/autoconfigure/client/WorkflowTestApplication.java @@ -0,0 +1,24 @@ +package io.dapr.spring.boot.autoconfigure.client; + +import io.dapr.spring.workflows.config.EnableDaprWorkflows; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; + +@SpringBootApplication +@EnableDaprWorkflows +public class WorkflowTestApplication { + public static void main(String[] args) { + SpringApplication.run(WorkflowTestApplication.class, args); + } + + @Configuration + static class Config { + @Bean + RestTemplate restTemplate(){ + return new RestTemplate(); + } + } +} diff --git a/dapr-spring/dapr-spring-boot-autoconfigure/src/test/java/io/dapr/spring/boot/autoconfigure/client/workflows/TestActivity.java b/dapr-spring/dapr-spring-boot-autoconfigure/src/test/java/io/dapr/spring/boot/autoconfigure/client/workflows/TestActivity.java new file mode 100644 index 000000000..67e6715f0 --- /dev/null +++ b/dapr-spring/dapr-spring-boot-autoconfigure/src/test/java/io/dapr/spring/boot/autoconfigure/client/workflows/TestActivity.java @@ -0,0 +1,21 @@ +package io.dapr.spring.boot.autoconfigure.client.workflows; + +import io.dapr.workflows.runtime.WorkflowActivity; +import io.dapr.workflows.runtime.WorkflowActivityContext; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.client.RestTemplate; + +public class TestActivity implements WorkflowActivity { + + @Autowired + private RestTemplate restTemplate; + + @Override + public Object run(WorkflowActivityContext ctx) { + return "OK"; + } + + public RestTemplate getRestTemplate() { + return restTemplate; + } +} diff --git a/dapr-spring/dapr-spring-boot-autoconfigure/src/test/java/io/dapr/spring/boot/autoconfigure/client/workflows/TestWorkflow.java b/dapr-spring/dapr-spring-boot-autoconfigure/src/test/java/io/dapr/spring/boot/autoconfigure/client/workflows/TestWorkflow.java new file mode 100644 index 000000000..97e94b1e7 --- /dev/null +++ b/dapr-spring/dapr-spring-boot-autoconfigure/src/test/java/io/dapr/spring/boot/autoconfigure/client/workflows/TestWorkflow.java @@ -0,0 +1,23 @@ +package io.dapr.spring.boot.autoconfigure.client.workflows; + +import io.dapr.workflows.Workflow; +import io.dapr.workflows.WorkflowStub; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.client.RestTemplate; + +public class TestWorkflow extends Workflow { + + @Autowired + private RestTemplate restTemplate; + + @Override + public WorkflowStub create() { + return ctx -> { + ctx.callActivity(TestActivity.class.getName(), null).await(); + }; + } + + public RestTemplate getRestTemplate() { + return restTemplate; + } +} diff --git a/dapr-spring/dapr-spring-boot-starters/dapr-spring-boot-starter/pom.xml b/dapr-spring/dapr-spring-boot-starters/dapr-spring-boot-starter/pom.xml index e2ce4bf1b..682ce450e 100644 --- a/dapr-spring/dapr-spring-boot-starters/dapr-spring-boot-starter/pom.xml +++ b/dapr-spring/dapr-spring-boot-starters/dapr-spring-boot-starter/pom.xml @@ -40,6 +40,11 @@ dapr-spring-messaging ${project.parent.version} + + io.dapr.spring + dapr-spring-workflows + ${project.parent.version} + diff --git a/dapr-spring/dapr-spring-workflows/pom.xml b/dapr-spring/dapr-spring-workflows/pom.xml new file mode 100644 index 000000000..1b9776943 --- /dev/null +++ b/dapr-spring/dapr-spring-workflows/pom.xml @@ -0,0 +1,24 @@ + + + 4.0.0 + + + io.dapr.spring + dapr-spring-parent + 0.14.0-SNAPSHOT + + + dapr-spring-workflows + dapr-spring-workflows + Dapr Spring Workflows + jar + + + + io.dapr + dapr-sdk-workflows + ${project.version} + + + diff --git a/dapr-spring/dapr-spring-workflows/src/main/java/io/dapr/spring/workflows/config/DaprWorkflowsConfiguration.java b/dapr-spring/dapr-spring-workflows/src/main/java/io/dapr/spring/workflows/config/DaprWorkflowsConfiguration.java new file mode 100644 index 000000000..35753fb47 --- /dev/null +++ b/dapr-spring/dapr-spring-workflows/src/main/java/io/dapr/spring/workflows/config/DaprWorkflowsConfiguration.java @@ -0,0 +1,55 @@ +package io.dapr.spring.workflows.config; + +import io.dapr.workflows.Workflow; +import io.dapr.workflows.runtime.WorkflowActivity; +import io.dapr.workflows.runtime.WorkflowRuntime; +import io.dapr.workflows.runtime.WorkflowRuntimeBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.annotation.Configuration; + +import java.util.Map; + +@Configuration +public class DaprWorkflowsConfiguration implements ApplicationContextAware { + private static final Logger LOGGER = LoggerFactory.getLogger(DaprWorkflowsConfiguration.class); + + private WorkflowRuntimeBuilder workflowRuntimeBuilder; + + public DaprWorkflowsConfiguration(WorkflowRuntimeBuilder workflowRuntimeBuilder) { + this.workflowRuntimeBuilder = workflowRuntimeBuilder; + } + + /** + * Register workflows and activities to the workflowRuntimeBuilder. + * @param applicationContext Spring Application Context + */ + private void registerWorkflowsAndActivities(ApplicationContext applicationContext) { + LOGGER.info("Registering Dapr Workflows and Activities"); + Map workflowBeans = applicationContext.getBeansOfType(Workflow.class); + for (Workflow w : workflowBeans.values()) { + LOGGER.info("Dapr Workflow: '{}' registered", w.getClass().getName()); + workflowRuntimeBuilder.registerWorkflow(w.getClass()); + } + + Map workflowActivitiesBeans = applicationContext.getBeansOfType(WorkflowActivity.class); + for (WorkflowActivity a : workflowActivitiesBeans.values()) { + LOGGER.info("Dapr Workflow Activity: '{}' registered", a.getClass().getName()); + workflowRuntimeBuilder.registerActivity(a.getClass()); + } + + try (WorkflowRuntime runtime = workflowRuntimeBuilder.build()) { + LOGGER.info("Starting workflow runtime ... "); + runtime.start(false); + } + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + registerWorkflowsAndActivities(applicationContext); + } +} diff --git a/dapr-spring/dapr-spring-workflows/src/main/java/io/dapr/spring/workflows/config/EnableDaprWorkflows.java b/dapr-spring/dapr-spring-workflows/src/main/java/io/dapr/spring/workflows/config/EnableDaprWorkflows.java new file mode 100644 index 000000000..e5572f6f7 --- /dev/null +++ b/dapr-spring/dapr-spring-workflows/src/main/java/io/dapr/spring/workflows/config/EnableDaprWorkflows.java @@ -0,0 +1,16 @@ +package io.dapr.spring.workflows.config; + + +import org.springframework.context.annotation.Import; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Retention(RUNTIME) +@Target(TYPE) +@Import(DaprWorkflowsConfiguration.class) +public @interface EnableDaprWorkflows { +} diff --git a/dapr-spring/pom.xml b/dapr-spring/pom.xml index 553c9aab4..ffbb736f3 100644 --- a/dapr-spring/pom.xml +++ b/dapr-spring/pom.xml @@ -20,6 +20,7 @@ dapr-spring-data dapr-spring-messaging + dapr-spring-workflows dapr-spring-boot-autoconfigure dapr-spring-boot-tests dapr-spring-boot-starters/dapr-spring-boot-starter