From 14764a8eaa7aee420c51f0f928139d5986fd204b Mon Sep 17 00:00:00 2001 From: Christian Kaps Date: Tue, 14 Jan 2025 16:16:08 +0100 Subject: [PATCH 01/21] Add actor testcontainer tests Signed-off-by: Christian Kaps --- .../dapr/it/testcontainers/DaprActorsIT.java | 73 +++++++++++++++++++ .../io/dapr/it/testcontainers/TestActor.java | 9 +++ .../dapr/it/testcontainers/TestActorImpl.java | 16 ++++ .../testcontainers/TestActorsApplication.java | 25 +++++++ .../TestDaprActorsConfiguration.java | 26 +++++++ 5 files changed, 149 insertions(+) create mode 100644 sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprActorsIT.java create mode 100644 sdk-tests/src/test/java/io/dapr/it/testcontainers/TestActor.java create mode 100644 sdk-tests/src/test/java/io/dapr/it/testcontainers/TestActorImpl.java create mode 100644 sdk-tests/src/test/java/io/dapr/it/testcontainers/TestActorsApplication.java create mode 100644 sdk-tests/src/test/java/io/dapr/it/testcontainers/TestDaprActorsConfiguration.java diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprActorsIT.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprActorsIT.java new file mode 100644 index 0000000000..678feba6b6 --- /dev/null +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprActorsIT.java @@ -0,0 +1,73 @@ +package io.dapr.it.testcontainers; + +import io.dapr.actors.ActorId; +import io.dapr.actors.client.ActorClient; +import io.dapr.actors.client.ActorProxyBuilder; +import io.dapr.testcontainers.Component; +import io.dapr.testcontainers.DaprContainer; +import io.dapr.testcontainers.DaprLogLevel; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.testcontainers.containers.Network; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.util.Map; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@SpringBootTest( + webEnvironment = WebEnvironment.RANDOM_PORT, + classes = { + TestDaprActorsConfiguration.class, + TestActorsApplication.class + } +) +@Testcontainers +@Tag("testcontainers") +public class DaprActorsIT { + private static final Network DAPR_NETWORK = Network.newNetwork(); + + @Container + private static final DaprContainer DAPR_CONTAINER = new DaprContainer("daprio/daprd:1.13.2") + .withAppName("actor-dapr-app") + .withNetwork(DAPR_NETWORK) + .withComponent(new Component("kvstore", "state.in-memory", "v1", + Map.of("actorStateStore", "true"))) + .withDaprLogLevel(DaprLogLevel.DEBUG) + .withLogConsumer(outputFrame -> System.out.println(outputFrame.getUtf8String())) + .withAppChannelAddress("host.testcontainers.internal"); + + /** + * Expose the Dapr ports to the host. + * + * @param registry the dynamic property registry + */ + @DynamicPropertySource + static void daprProperties(DynamicPropertyRegistry registry) { + registry.add("dapr.http.endpoint", DAPR_CONTAINER::getHttpEndpoint); + registry.add("dapr.grpc.endpoint", DAPR_CONTAINER::getGrpcEndpoint); + } + + @Autowired + private ActorClient actorClient; + + @Test + public void testWorkflows() throws Exception { + ActorProxyBuilder builder = new ActorProxyBuilder(TestActor.class, actorClient); + ActorId actorId = ActorId.createRandom(); + TestActor actor = builder.build(actorId); + + String message = UUID.randomUUID().toString(); + + String echoedMessage = actor.echo(message); + + assertEquals(echoedMessage, message); + } +} diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestActor.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestActor.java new file mode 100644 index 0000000000..a3f8ef6d06 --- /dev/null +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestActor.java @@ -0,0 +1,9 @@ +package io.dapr.it.testcontainers; +import io.dapr.actors.ActorMethod; +import io.dapr.actors.ActorType; + +@ActorType(name = "TestActor") +public interface TestActor { + @ActorMethod(name = "echo_message") + String echo(String message); +} diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestActorImpl.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestActorImpl.java new file mode 100644 index 0000000000..0a5febddef --- /dev/null +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestActorImpl.java @@ -0,0 +1,16 @@ +package io.dapr.it.testcontainers; + +import io.dapr.actors.ActorId; +import io.dapr.actors.runtime.AbstractActor; +import io.dapr.actors.runtime.ActorRuntimeContext; + +public class TestActorImpl extends AbstractActor implements TestActor { + public TestActorImpl(ActorRuntimeContext runtimeContext, ActorId id) { + super(runtimeContext, id); + } + + @Override + public String echo(String message) { + return message; + } +} diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestActorsApplication.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestActorsApplication.java new file mode 100644 index 0000000000..ba9a748c8a --- /dev/null +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestActorsApplication.java @@ -0,0 +1,25 @@ +/* + * Copyright 2024 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file 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.dapr.it.testcontainers; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class TestActorsApplication { + + public static void main(String[] args) { + SpringApplication.run(TestActorsApplication.class, args); + } +} diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestDaprActorsConfiguration.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestDaprActorsConfiguration.java new file mode 100644 index 0000000000..5d169c28fa --- /dev/null +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestDaprActorsConfiguration.java @@ -0,0 +1,26 @@ +package io.dapr.it.testcontainers; + +import java.util.Map; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import io.dapr.actors.client.ActorClient; +import io.dapr.config.Properties; + +@Configuration +public class TestDaprActorsConfiguration { + @Bean + public ActorClient daprActorClient( + @Value("${dapr.http.endpoint}") String daprHttpEndpoint, + @Value("${dapr.grpc.endpoint}") String daprGrpcEndpoint + ){ + Map overrides = Map.of( + "dapr.http.endpoint", daprHttpEndpoint, + "dapr.grpc.endpoint", daprGrpcEndpoint + ); + + return new ActorClient(new Properties(overrides)); + } +} From 83c8776e5f880ece9143a43071e6b020f8d04e4f Mon Sep 17 00:00:00 2001 From: salaboy Date: Wed, 22 Jan 2025 14:39:58 +0000 Subject: [PATCH 02/21] adding auto config Signed-off-by: Christian Kaps --- .../client/DaprClientAutoConfiguration.java | 8 ++++++++ .../java/io/dapr/it/testcontainers/DaprActorsIT.java | 9 ++++----- .../java/io/dapr/it/testcontainers/DaprWorkflowsIT.java | 2 +- 3 files changed, 13 insertions(+), 6 deletions(-) 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 7e10c1f8fe..bc81885aa0 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 @@ -13,6 +13,7 @@ package io.dapr.spring.boot.autoconfigure.client; +import io.dapr.actors.client.ActorClient; import io.dapr.client.DaprClient; import io.dapr.client.DaprClientBuilder; import io.dapr.config.Properties; @@ -70,6 +71,13 @@ DaprWorkflowClient daprWorkflowClient(DaprConnectionDetails daprConnectionDetail return new DaprWorkflowClient(properties); } + @Bean + @ConditionalOnMissingBean + ActorClient daprActorClient(DaprConnectionDetails daprConnectionDetails) { + Properties properties = createPropertiesFromConnectionDetails(daprConnectionDetails); + return new ActorClient(properties); + } + @Bean @ConditionalOnMissingBean WorkflowRuntimeBuilder daprWorkflowRuntimeBuilder(DaprConnectionDetails daprConnectionDetails) { diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprActorsIT.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprActorsIT.java index 678feba6b6..2c26832ccc 100644 --- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprActorsIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprActorsIT.java @@ -25,7 +25,6 @@ @SpringBootTest( webEnvironment = WebEnvironment.RANDOM_PORT, classes = { - TestDaprActorsConfiguration.class, TestActorsApplication.class } ) @@ -35,7 +34,7 @@ public class DaprActorsIT { private static final Network DAPR_NETWORK = Network.newNetwork(); @Container - private static final DaprContainer DAPR_CONTAINER = new DaprContainer("daprio/daprd:1.13.2") + private static final DaprContainer DAPR_CONTAINER = new DaprContainer("daprio/daprd:1.14.1") .withAppName("actor-dapr-app") .withNetwork(DAPR_NETWORK) .withComponent(new Component("kvstore", "state.in-memory", "v1", @@ -56,11 +55,11 @@ static void daprProperties(DynamicPropertyRegistry registry) { } @Autowired - private ActorClient actorClient; + private ActorClient daprActorClient; @Test - public void testWorkflows() throws Exception { - ActorProxyBuilder builder = new ActorProxyBuilder(TestActor.class, actorClient); + public void testActors() throws Exception { + ActorProxyBuilder builder = new ActorProxyBuilder(TestActor.class, daprActorClient); ActorId actorId = ActorId.createRandom(); TestActor actor = builder.build(actorId); diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprWorkflowsIT.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprWorkflowsIT.java index f0c39ed803..0e71ce647c 100644 --- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprWorkflowsIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprWorkflowsIT.java @@ -56,7 +56,7 @@ public class DaprWorkflowsIT { private static final Network DAPR_NETWORK = Network.newNetwork(); @Container - private static final DaprContainer DAPR_CONTAINER = new DaprContainer("daprio/daprd:1.13.2") + private static final DaprContainer DAPR_CONTAINER = new DaprContainer("daprio/daprd:1.14.1") .withAppName("workflow-dapr-app") .withNetwork(DAPR_NETWORK) .withComponent(new Component("kvstore", "state.in-memory", "v1", From b9ff56f7839b70267cf297d763819d20a9feb223 Mon Sep 17 00:00:00 2001 From: salaboy Date: Wed, 22 Jan 2025 17:43:00 +0000 Subject: [PATCH 03/21] updating ActorClient Signed-off-by: Christian Kaps --- .../io/dapr/actors/client/ActorClient.java | 22 ++----------------- .../java/io/dapr/it/actors/ActorStateIT.java | 2 +- .../dapr/it/testcontainers/DaprActorsIT.java | 7 ++++-- 3 files changed, 8 insertions(+), 23 deletions(-) diff --git a/sdk-actors/src/main/java/io/dapr/actors/client/ActorClient.java b/sdk-actors/src/main/java/io/dapr/actors/client/ActorClient.java index 08c0a1c9ec..1fe2d4f19d 100644 --- a/sdk-actors/src/main/java/io/dapr/actors/client/ActorClient.java +++ b/sdk-actors/src/main/java/io/dapr/actors/client/ActorClient.java @@ -15,6 +15,7 @@ import io.dapr.client.resiliency.ResiliencyOptions; import io.dapr.config.Properties; +import io.dapr.utils.NetworkUtils; import io.dapr.utils.Version; import io.dapr.v1.DaprGrpc; import io.grpc.Channel; @@ -83,7 +84,7 @@ public ActorClient(Properties overrideProperties, ResiliencyOptions resiliencyOp * @param resiliencyOptions Client resiliency options. */ public ActorClient(Properties overrideProperties, Map metadata, ResiliencyOptions resiliencyOptions) { - this(buildManagedChannel(overrideProperties), + this(NetworkUtils.buildGrpcManagedChannel(overrideProperties), metadata, resiliencyOptions, overrideProperties.getValue(Properties.API_TOKEN)); @@ -129,25 +130,6 @@ public void close() { } } - /** - * Creates a GRPC managed channel (or null, if not applicable). - * - * @param overrideProperties Overrides - * @return GRPC managed channel or null. - */ - private static ManagedChannel buildManagedChannel(Properties overrideProperties) { - int port = overrideProperties.getValue(Properties.GRPC_PORT); - if (port <= 0) { - throw new IllegalArgumentException("Invalid port."); - } - - var sidecarHost = overrideProperties.getValue(Properties.SIDECAR_IP); - - return ManagedChannelBuilder.forAddress(sidecarHost, port) - .usePlaintext() - .userAgent(Version.getSdkVersion()) - .build(); - } /** * Build an instance of the Client based on the provided setup. diff --git a/sdk-tests/src/test/java/io/dapr/it/actors/ActorStateIT.java b/sdk-tests/src/test/java/io/dapr/it/actors/ActorStateIT.java index 67d6b9659c..90bab85e52 100644 --- a/sdk-tests/src/test/java/io/dapr/it/actors/ActorStateIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/actors/ActorStateIT.java @@ -141,7 +141,7 @@ public void writeReadState() throws Exception { proxyBuilder = new ActorProxyBuilder(actorType, ActorProxy.class, deferClose(run2.newActorClient())); ActorProxy newProxy = proxyBuilder.build(actorId); - // wating for actor to be activated + // waiting for actor to be activated Thread.sleep(2000); callWithRetry(() -> { diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprActorsIT.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprActorsIT.java index 2c26832ccc..2090cd0b4a 100644 --- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprActorsIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprActorsIT.java @@ -3,6 +3,7 @@ import io.dapr.actors.ActorId; import io.dapr.actors.client.ActorClient; import io.dapr.actors.client.ActorProxyBuilder; +import io.dapr.actors.runtime.ActorRuntime; import io.dapr.testcontainers.Component; import io.dapr.testcontainers.DaprContainer; import io.dapr.testcontainers.DaprLogLevel; @@ -25,7 +26,8 @@ @SpringBootTest( webEnvironment = WebEnvironment.RANDOM_PORT, classes = { - TestActorsApplication.class + TestActorsApplication.class, + TestDaprActorsConfiguration.class } ) @Testcontainers @@ -59,7 +61,8 @@ static void daprProperties(DynamicPropertyRegistry registry) { @Test public void testActors() throws Exception { - ActorProxyBuilder builder = new ActorProxyBuilder(TestActor.class, daprActorClient); + ActorRuntime.getInstance().registerActor(TestActorImpl.class); + ActorProxyBuilder builder = new ActorProxyBuilder<>(TestActor.class, daprActorClient); ActorId actorId = ActorId.createRandom(); TestActor actor = builder.build(actorId); From be84c35485489aaa3614e62f66caefc135b612e4 Mon Sep 17 00:00:00 2001 From: salaboy Date: Thu, 23 Jan 2025 14:36:38 +0000 Subject: [PATCH 04/21] registering? Signed-off-by: Christian Kaps --- .../test/java/io/dapr/it/testcontainers/DaprActorsIT.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprActorsIT.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprActorsIT.java index 2090cd0b4a..1a9107857f 100644 --- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprActorsIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprActorsIT.java @@ -7,6 +7,7 @@ import io.dapr.testcontainers.Component; import io.dapr.testcontainers.DaprContainer; import io.dapr.testcontainers.DaprLogLevel; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -56,12 +57,17 @@ static void daprProperties(DynamicPropertyRegistry registry) { registry.add("dapr.grpc.endpoint", DAPR_CONTAINER::getGrpcEndpoint); } + @BeforeAll + public static void setUp(){ + ActorRuntime.getInstance().registerActor(TestActorImpl.class); + } + @Autowired private ActorClient daprActorClient; @Test public void testActors() throws Exception { - ActorRuntime.getInstance().registerActor(TestActorImpl.class); + ActorProxyBuilder builder = new ActorProxyBuilder<>(TestActor.class, daprActorClient); ActorId actorId = ActorId.createRandom(); TestActor actor = builder.build(actorId); From 470f32e007084874b11649db1f7636f6dc3bd5ff Mon Sep 17 00:00:00 2001 From: salaboy Date: Mon, 3 Feb 2025 12:37:38 +0000 Subject: [PATCH 05/21] updating actors test and actorruntime Signed-off-by: Christian Kaps --- .../dapr/it/testcontainers/DaprActorsIT.java | 18 ++++++++++-------- .../TestDaprActorsConfiguration.java | 14 ++++++++++++++ 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprActorsIT.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprActorsIT.java index 1a9107857f..23dc6040fb 100644 --- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprActorsIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprActorsIT.java @@ -7,7 +7,7 @@ import io.dapr.testcontainers.Component; import io.dapr.testcontainers.DaprContainer; import io.dapr.testcontainers.DaprLogLevel; -import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -57,17 +57,19 @@ static void daprProperties(DynamicPropertyRegistry registry) { registry.add("dapr.grpc.endpoint", DAPR_CONTAINER::getGrpcEndpoint); } - @BeforeAll - public static void setUp(){ - ActorRuntime.getInstance().registerActor(TestActorImpl.class); - } - @Autowired private ActorClient daprActorClient; - @Test - public void testActors() throws Exception { + @Autowired + private ActorRuntime daprActorRuntime; + @BeforeEach + public void setUp(){ + daprActorRuntime.registerActor(TestActorImpl.class); + } + + @Test + public void testActors() throws Exception { ActorProxyBuilder builder = new ActorProxyBuilder<>(TestActor.class, daprActorClient); ActorId actorId = ActorId.createRandom(); TestActor actor = builder.build(actorId); diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestDaprActorsConfiguration.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestDaprActorsConfiguration.java index 5d169c28fa..cae0e1fa97 100644 --- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestDaprActorsConfiguration.java +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestDaprActorsConfiguration.java @@ -2,6 +2,7 @@ import java.util.Map; +import io.dapr.actors.runtime.ActorRuntime; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -23,4 +24,17 @@ public ActorClient daprActorClient( return new ActorClient(new Properties(overrides)); } + + @Bean + public ActorRuntime daprActorRuntime( + @Value("${dapr.http.endpoint}") String daprHttpEndpoint, + @Value("${dapr.grpc.endpoint}") String daprGrpcEndpoint + ){ + Map overrides = Map.of( + "dapr.http.endpoint", daprHttpEndpoint, + "dapr.grpc.endpoint", daprGrpcEndpoint + ); + + return ActorRuntime.getInstance(new Properties(overrides)); + } } From 638a2254196377abd0d28dc3db5d2408d2eae581 Mon Sep 17 00:00:00 2001 From: salaboy Date: Mon, 3 Feb 2025 12:38:33 +0000 Subject: [PATCH 06/21] updating ActorRuntime Signed-off-by: Christian Kaps --- .../client/DaprClientAutoConfiguration.java | 8 ++ .../io/dapr/actors/runtime/ActorRuntime.java | 103 ++++++++++-------- 2 files changed, 63 insertions(+), 48 deletions(-) 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 bc81885aa0..ebd6a18d7a 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 @@ -14,6 +14,7 @@ package io.dapr.spring.boot.autoconfigure.client; import io.dapr.actors.client.ActorClient; +import io.dapr.actors.runtime.ActorRuntime; import io.dapr.client.DaprClient; import io.dapr.client.DaprClientBuilder; import io.dapr.config.Properties; @@ -78,6 +79,13 @@ ActorClient daprActorClient(DaprConnectionDetails daprConnectionDetails) { return new ActorClient(properties); } + @Bean + @ConditionalOnMissingBean + ActorRuntime daprActorRuntime(DaprConnectionDetails daprConnectionDetails) { + Properties properties = createPropertiesFromConnectionDetails(daprConnectionDetails); + return ActorRuntime.getInstance(properties); + } + @Bean @ConditionalOnMissingBean WorkflowRuntimeBuilder daprWorkflowRuntimeBuilder(DaprConnectionDetails daprConnectionDetails) { diff --git a/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorRuntime.java b/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorRuntime.java index 8eb09bc7db..d329946e5a 100644 --- a/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorRuntime.java +++ b/sdk-actors/src/main/java/io/dapr/actors/runtime/ActorRuntime.java @@ -18,9 +18,8 @@ import io.dapr.config.Properties; import io.dapr.serializer.DaprObjectSerializer; import io.dapr.serializer.DefaultObjectSerializer; -import io.dapr.utils.Version; +import io.dapr.utils.NetworkUtils; import io.grpc.ManagedChannel; -import io.grpc.ManagedChannelBuilder; import reactor.core.publisher.Mono; import java.io.Closeable; @@ -80,23 +79,32 @@ public class ActorRuntime implements Closeable { * @throws IllegalStateException If cannot instantiate Runtime. */ private ActorRuntime() throws IllegalStateException { - this(buildManagedChannel()); + this(new Properties()); + } + + /** + * The default constructor. This should not be called directly. + * + * @throws IllegalStateException If cannot instantiate Runtime. + */ + private ActorRuntime(Properties properties) throws IllegalStateException { + this(NetworkUtils.buildGrpcManagedChannel(properties)); } /** * Constructor once channel is available. This should not be called directly. * * @param channel GRPC managed channel to be closed (or null). - * @throws IllegalStateException If cannot instantiate Runtime. + * @throws IllegalStateException If you cannot instantiate Runtime. */ private ActorRuntime(ManagedChannel channel) throws IllegalStateException { - this(channel, buildDaprClient(channel)); + this(channel, new DaprClientImpl(channel)); } /** * Constructor with dependency injection, useful for testing. This should not be called directly. * - * @param channel GRPC managed channel to be closed (or null). + * @param channel GRPC managed channel to be closed (or null). * @param daprClient Client to communicate with Dapr. * @throws IllegalStateException If class has one instance already. */ @@ -128,6 +136,24 @@ public static ActorRuntime getInstance() { return instance; } + /** + * Returns an ActorRuntime object. + * + * @param properties Properties to be used for the runtime. + * @return An ActorRuntime object. + */ + public static ActorRuntime getInstance(Properties properties) { + if (instance == null) { + synchronized (ActorRuntime.class) { + if (instance == null) { + instance = new ActorRuntime(properties); + } + } + } + + return instance; + } + /** * Gets the Actor configuration for this runtime. * @@ -149,11 +175,10 @@ public byte[] serializeConfig() throws IOException { /** * Registers an actor with the runtime, using {@link DefaultObjectSerializer} and {@link DefaultActorFactory}. - * * {@link DefaultObjectSerializer} is not recommended for production scenarios. * - * @param clazz The type of actor. - * @param Actor class type. + * @param clazz The type of actor. + * @param Actor class type. */ public void registerActor(Class clazz) { registerActor(clazz, new DefaultObjectSerializer(), new DefaultObjectSerializer()); @@ -161,12 +186,11 @@ public void registerActor(Class clazz) { /** * Registers an actor with the runtime, using {@link DefaultObjectSerializer}. - * * {@link DefaultObjectSerializer} is not recommended for production scenarios. * - * @param clazz The type of actor. - * @param actorFactory An optional factory to create actors. This can be used for dependency injection. - * @param Actor class type. + * @param clazz The type of actor. + * @param actorFactory An optional factory to create actors. This can be used for dependency injection. + * @param Actor class type. */ public void registerActor(Class clazz, ActorFactory actorFactory) { registerActor(clazz, actorFactory, new DefaultObjectSerializer(), new DefaultObjectSerializer()); @@ -181,8 +205,8 @@ public void registerActor(Class clazz, ActorFactory * @param Actor class type. */ public void registerActor( - Class clazz, DaprObjectSerializer objectSerializer, DaprObjectSerializer stateSerializer) { - registerActor(clazz, new DefaultActorFactory(), objectSerializer, stateSerializer); + Class clazz, DaprObjectSerializer objectSerializer, DaprObjectSerializer stateSerializer) { + registerActor(clazz, new DefaultActorFactory(), objectSerializer, stateSerializer); } /** @@ -195,9 +219,9 @@ public void registerActor( * @param Actor class type. */ public void registerActor( - Class clazz, ActorFactory actorFactory, - DaprObjectSerializer objectSerializer, - DaprObjectSerializer stateSerializer) { + Class clazz, ActorFactory actorFactory, + DaprObjectSerializer objectSerializer, + DaprObjectSerializer stateSerializer) { if (clazz == null) { throw new IllegalArgumentException("Class is required."); } @@ -216,12 +240,12 @@ public void registerActor( // Create ActorManager, if not yet registered. this.actorManagers.computeIfAbsent(actorTypeInfo.getName(), (k) -> { ActorRuntimeContext context = new ActorRuntimeContext<>( - this, - objectSerializer, - actorFactory, - actorTypeInfo, - this.daprClient, - new DaprStateAsyncProvider(this.daprClient, stateSerializer)); + this, + objectSerializer, + actorFactory, + actorTypeInfo, + this.daprClient, + new DaprStateAsyncProvider(this.daprClient, stateSerializer)); this.config.addRegisteredActorType(actorTypeInfo.getName()); return new ActorManager(context); }); @@ -236,7 +260,7 @@ public void registerActor( */ public Mono deactivate(String actorTypeName, String actorId) { return Mono.fromSupplier(() -> this.getActorManager(actorTypeName)) - .flatMap(m -> m.deactivateActor(new ActorId(actorId))); + .flatMap(m -> m.deactivateActor(new ActorId(actorId))); } /** @@ -252,8 +276,8 @@ public Mono deactivate(String actorTypeName, String actorId) { public Mono invoke(String actorTypeName, String actorId, String actorMethodName, byte[] payload) { ActorId id = new ActorId(actorId); return Mono.fromSupplier(() -> this.getActorManager(actorTypeName)) - .flatMap(m -> m.activateActor(id).thenReturn(m)) - .flatMap(m -> ((ActorManager)m).invokeMethod(id, actorMethodName, payload)); + .flatMap(m -> m.activateActor(id).thenReturn(m)) + .flatMap(m -> ((ActorManager) m).invokeMethod(id, actorMethodName, payload)); } /** @@ -268,8 +292,8 @@ public Mono invoke(String actorTypeName, String actorId, String actorMet public Mono invokeReminder(String actorTypeName, String actorId, String reminderName, byte[] params) { ActorId id = new ActorId(actorId); return Mono.fromSupplier(() -> this.getActorManager(actorTypeName)) - .flatMap(m -> m.activateActor(id).thenReturn(m)) - .flatMap(m -> ((ActorManager)m).invokeReminder(new ActorId(actorId), reminderName, params)); + .flatMap(m -> m.activateActor(id).thenReturn(m)) + .flatMap(m -> ((ActorManager) m).invokeReminder(new ActorId(actorId), reminderName, params)); } /** @@ -284,8 +308,8 @@ public Mono invokeReminder(String actorTypeName, String actorId, String re public Mono invokeTimer(String actorTypeName, String actorId, String timerName, byte[] params) { ActorId id = new ActorId(actorId); return Mono.fromSupplier(() -> this.getActorManager(actorTypeName)) - .flatMap(m -> m.activateActor(id).thenReturn(m)) - .flatMap(m -> ((ActorManager)m).invokeTimer(new ActorId(actorId), timerName, params)); + .flatMap(m -> m.activateActor(id).thenReturn(m)) + .flatMap(m -> ((ActorManager) m).invokeTimer(new ActorId(actorId), timerName, params)); } /** @@ -318,23 +342,6 @@ private static DaprClient buildDaprClient(ManagedChannel channel) { return new DaprClientImpl(channel); } - /** - * Creates a GRPC managed channel (or null, if not applicable). - * - * @return GRPC managed channel or null. - */ - private static ManagedChannel buildManagedChannel() { - int port = Properties.GRPC_PORT.get(); - if (port <= 0) { - throw new IllegalStateException("Invalid port."); - } - - return ManagedChannelBuilder.forAddress(Properties.SIDECAR_IP.get(), port) - .usePlaintext() - .userAgent(Version.getSdkVersion()) - .build(); - } - /** * {@inheritDoc} */ From 9c29db9adcc58a842bb4ab60d462387079fde3cd Mon Sep 17 00:00:00 2001 From: artur-ciocanu Date: Thu, 30 Jan 2025 02:22:07 +0200 Subject: [PATCH 07/21] Adding WorkflowTaskOptions and use it instead of TaskOptions (#1200) Signed-off-by: Christian Kaps --- .../main/java/io/dapr/workflows/Workflow.java | 4 +- .../workflows/WorkflowActivityContext.java | 2 + .../io/dapr/workflows/WorkflowContext.java | 39 ++-- .../java/io/dapr/workflows/WorkflowStub.java | 2 + .../dapr/workflows/WorkflowTaskOptions.java | 28 +++ .../workflows/WorkflowTaskRetryPolicy.java | 180 ++++++++++++++++++ .../workflows/client/DaprWorkflowClient.java | 13 +- ...lowOption.java => NewWorkflowOptions.java} | 18 +- .../runtime/DefaultWorkflowContext.java | 35 +++- .../saga/CompensationInformation.java | 14 +- .../java/io/dapr/workflows/saga/Saga.java | 26 +-- .../{SagaOption.java => SagaOptions.java} | 8 +- .../workflows/DefaultWorkflowContextTest.java | 55 ++++-- .../java/io/dapr/workflows/WorkflowTest.java | 6 +- .../client/DaprWorkflowClientTest.java | 8 +- ...nTest.java => NewWorkflowOptionsTest.java} | 4 +- .../workflows/saga/SagaIntegrationTest.java | 2 +- ...gaOptionTest.java => SagaOptionsTest.java} | 12 +- .../java/io/dapr/workflows/saga/SagaTest.java | 122 ++++++------ 19 files changed, 410 insertions(+), 168 deletions(-) create mode 100644 sdk-workflows/src/main/java/io/dapr/workflows/WorkflowTaskOptions.java create mode 100644 sdk-workflows/src/main/java/io/dapr/workflows/WorkflowTaskRetryPolicy.java rename sdk-workflows/src/main/java/io/dapr/workflows/client/{NewWorkflowOption.java => NewWorkflowOptions.java} (86%) rename sdk-workflows/src/main/java/io/dapr/workflows/saga/{SagaOption.java => SagaOptions.java} (91%) rename sdk-workflows/src/test/java/io/dapr/workflows/client/{NewWorkflowOptionTest.java => NewWorkflowOptionsTest.java} (87%) rename sdk-workflows/src/test/java/io/dapr/workflows/saga/{SagaOptionTest.java => SagaOptionsTest.java} (78%) diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/Workflow.java b/sdk-workflows/src/main/java/io/dapr/workflows/Workflow.java index 0b1a85f02b..8cb4750aeb 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/Workflow.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/Workflow.java @@ -16,7 +16,7 @@ import com.microsoft.durabletask.interruption.ContinueAsNewInterruption; import com.microsoft.durabletask.interruption.OrchestratorBlockedException; import io.dapr.workflows.saga.SagaCompensationException; -import io.dapr.workflows.saga.SagaOption; +import io.dapr.workflows.saga.SagaOptions; /** * Common interface for workflow implementations. @@ -74,7 +74,7 @@ default boolean isSagaEnabled() { * * @return saga configuration */ - default SagaOption getSagaOption() { + default SagaOptions getSagaOption() { // by default, saga is disabled return null; } diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowActivityContext.java b/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowActivityContext.java index 114c8b58e7..3fe5d88a23 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowActivityContext.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowActivityContext.java @@ -14,7 +14,9 @@ package io.dapr.workflows; public interface WorkflowActivityContext { + String getName(); T getInput(Class targetType); + } diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowContext.java b/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowContext.java index bc5d53186b..4156c5a592 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowContext.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowContext.java @@ -17,7 +17,6 @@ import com.microsoft.durabletask.Task; import com.microsoft.durabletask.TaskCanceledException; import com.microsoft.durabletask.TaskFailedException; -import com.microsoft.durabletask.TaskOptions; import io.dapr.workflows.saga.SagaContext; import org.slf4j.Logger; @@ -153,15 +152,15 @@ default Task waitForExternalEvent(String name, Class dataType) { * @param the expected type of the activity output * @return a new {@link Task} that completes when the activity completes or fails */ - Task callActivity(String name, Object input, TaskOptions options, Class returnType); + Task callActivity(String name, Object input, WorkflowTaskOptions options, Class returnType); /** * Asynchronously invokes an activity by name and returns a new {@link Task} that completes when the activity - * completes. See {@link #callActivity(String, Object, TaskOptions, Class)} for a complete description. + * completes. See {@link #callActivity(String, Object, WorkflowTaskOptions, Class)} for a complete description. * * @param name the name of the activity to call * @return a new {@link Task} that completes when the activity completes or fails - * @see #callActivity(String, Object, TaskOptions, Class) + * @see #callActivity(String, Object, WorkflowTaskOptions, Class) */ default Task callActivity(String name) { return this.callActivity(name, null, null, Void.class); @@ -169,8 +168,8 @@ default Task callActivity(String name) { /** * Asynchronously invokes an activity by name and with the specified input value and returns a new {@link Task} - * that completes when the activity completes. See {@link #callActivity(String, Object, TaskOptions, Class)} for a - * complete description. + * that completes when the activity completes. + * See {@link #callActivity(String, Object, WorkflowTaskOptions, Class)} for a complete description. * * @param name the name of the activity to call * @param input the serializable input to pass to the activity @@ -183,7 +182,7 @@ default Task callActivity(String name, Object input) { /** * Asynchronously invokes an activity by name and returns a new {@link Task} that completes when the activity * completes. If the activity completes successfully, the returned {@code Task}'s value will be the activity's - * output. See {@link #callActivity(String, Object, TaskOptions, Class)} for a complete description. + * output. See {@link #callActivity(String, Object, WorkflowTaskOptions, Class)} for a complete description. * * @param name the name of the activity to call * @param returnType the expected class type of the activity output @@ -197,8 +196,8 @@ default Task callActivity(String name, Class returnType) { /** * Asynchronously invokes an activity by name and with the specified input value and returns a new {@link Task} * that completes when the activity completes.If the activity completes successfully, the returned {@code Task}'s - * value will be the activity's output. See {@link #callActivity(String, Object, TaskOptions, Class)} for a - * complete description. + * value will be the activity's output. + * See {@link #callActivity(String, Object, WorkflowTaskOptions, Class)} for a complete description. * * @param name the name of the activity to call * @param input the serializable input to pass to the activity @@ -212,15 +211,15 @@ default Task callActivity(String name, Object input, Class returnType) /** * Asynchronously invokes an activity by name and with the specified input value and returns a new {@link Task} - * that completes when the activity completes. See {@link #callActivity(String, Object, TaskOptions, Class)} for a - * complete description. + * that completes when the activity completes. + * See {@link #callActivity(String, Object, WorkflowTaskOptions, Class)} for a complete description. * * @param name the name of the activity to call * @param input the serializable input to pass to the activity * @param options additional options that control the execution and processing of the activity * @return a new {@link Task} that completes when the activity completes or fails */ - default Task callActivity(String name, Object input, TaskOptions options) { + default Task callActivity(String name, Object input, WorkflowTaskOptions options) { return this.callActivity(name, input, options, Void.class); } @@ -367,11 +366,11 @@ default Task createTimer(ZonedDateTime zonedDateTime) { * Asynchronously invokes another workflow as a child-workflow and returns a {@link Task} that completes * when the child-workflow completes. * - *

See {@link #callChildWorkflow(String, Object, String, TaskOptions, Class)} for a full description. + *

See {@link #callChildWorkflow(String, Object, String, WorkflowTaskOptions, Class)} for a full description. * * @param name the name of the workflow to invoke * @return a new {@link Task} that completes when the child-workflow completes or fails - * @see #callChildWorkflow(String, Object, String, TaskOptions, Class) + * @see #callChildWorkflow(String, Object, String, WorkflowTaskOptions, Class) */ default Task callChildWorkflow(String name) { return this.callChildWorkflow(name, null); @@ -381,7 +380,7 @@ default Task callChildWorkflow(String name) { * Asynchronously invokes another workflow as a child-workflow and returns a {@link Task} that completes * when the child-workflow completes. * - *

See {@link #callChildWorkflow(String, Object, String, TaskOptions, Class)} for a full description. + *

See {@link #callChildWorkflow(String, Object, String, WorkflowTaskOptions, Class)} for a full description. * * @param name the name of the workflow to invoke * @param input the serializable input to send to the child-workflow @@ -395,7 +394,7 @@ default Task callChildWorkflow(String name, Object input) { * Asynchronously invokes another workflow as a child-workflow and returns a {@link Task} that completes * when the child-workflow completes. * - *

See {@link #callChildWorkflow(String, Object, String, TaskOptions, Class)} for a full description. + *

See {@link #callChildWorkflow(String, Object, String, WorkflowTaskOptions, Class)} for a full description. * * @param name the name of the workflow to invoke * @param input the serializable input to send to the child-workflow @@ -411,7 +410,7 @@ default Task callChildWorkflow(String name, Object input, Class return * Asynchronously invokes another workflow as a child-workflow and returns a {@link Task} that completes * when the child-workflow completes. * - *

See {@link #callChildWorkflow(String, Object, String, TaskOptions, Class)} for a full description. + *

See {@link #callChildWorkflow(String, Object, String, WorkflowTaskOptions, Class)} for a full description. * * @param name the name of the workflow to invoke * @param input the serializable input to send to the child-workflow @@ -428,7 +427,7 @@ default Task callChildWorkflow(String name, Object input, String instance * Asynchronously invokes another workflow as a child-workflow and returns a {@link Task} that completes * when the child-workflow completes. * - *

See {@link #callChildWorkflow(String, Object, String, TaskOptions, Class)} for a full description. + *

See {@link #callChildWorkflow(String, Object, String, WorkflowTaskOptions, Class)} for a full description. * * @param name the name of the workflow to invoke * @param input the serializable input to send to the child-workflow @@ -436,7 +435,7 @@ default Task callChildWorkflow(String name, Object input, String instance * @param options additional options that control the execution and processing of the activity * @return a new {@link Task} that completes when the child-workflow completes or fails */ - default Task callChildWorkflow(String name, Object input, String instanceID, TaskOptions options) { + default Task callChildWorkflow(String name, Object input, String instanceID, WorkflowTaskOptions options) { return this.callChildWorkflow(name, input, instanceID, options, Void.class); } @@ -478,7 +477,7 @@ default Task callChildWorkflow(String name, Object input, String instanceI Task callChildWorkflow(String name, @Nullable Object input, @Nullable String instanceID, - @Nullable TaskOptions options, + @Nullable WorkflowTaskOptions options, Class returnType); /** diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowStub.java b/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowStub.java index 6a109c626d..ed8963e2d2 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowStub.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowStub.java @@ -15,5 +15,7 @@ @FunctionalInterface public interface WorkflowStub { + void run(WorkflowContext ctx); + } diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowTaskOptions.java b/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowTaskOptions.java new file mode 100644 index 0000000000..4f3be9d7f9 --- /dev/null +++ b/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowTaskOptions.java @@ -0,0 +1,28 @@ +/* + * Copyright 2025 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file 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.dapr.workflows; + +public class WorkflowTaskOptions { + + private final WorkflowTaskRetryPolicy retryPolicy; + + public WorkflowTaskOptions(WorkflowTaskRetryPolicy retryPolicy) { + this.retryPolicy = retryPolicy; + } + + public WorkflowTaskRetryPolicy getRetryPolicy() { + return retryPolicy; + } + +} diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowTaskRetryPolicy.java b/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowTaskRetryPolicy.java new file mode 100644 index 0000000000..cc63274ee5 --- /dev/null +++ b/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowTaskRetryPolicy.java @@ -0,0 +1,180 @@ +/* + * Copyright 2025 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file 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.dapr.workflows; + +import javax.annotation.Nullable; + +import java.time.Duration; + +public final class WorkflowTaskRetryPolicy { + + private final Integer maxNumberOfAttempts; + private final Duration firstRetryInterval; + private final Double backoffCoefficient; + private final Duration maxRetryInterval; + private final Duration retryTimeout; + + /** + * Constructor for WorkflowTaskRetryPolicy. + * @param maxNumberOfAttempts Maximum number of attempts to retry the workflow. + * @param firstRetryInterval Interval to wait before the first retry. + * @param backoffCoefficient Coefficient to increase the retry interval. + * @param maxRetryInterval Maximum interval to wait between retries. + * @param retryTimeout Timeout for the whole retry process. + */ + public WorkflowTaskRetryPolicy( + Integer maxNumberOfAttempts, + Duration firstRetryInterval, + Double backoffCoefficient, + Duration maxRetryInterval, + Duration retryTimeout + ) { + this.maxNumberOfAttempts = maxNumberOfAttempts; + this.firstRetryInterval = firstRetryInterval; + this.backoffCoefficient = backoffCoefficient; + this.maxRetryInterval = maxRetryInterval; + this.retryTimeout = retryTimeout; + } + + public int getMaxNumberOfAttempts() { + return maxNumberOfAttempts; + } + + public Duration getFirstRetryInterval() { + return firstRetryInterval; + } + + public double getBackoffCoefficient() { + return backoffCoefficient; + } + + public Duration getMaxRetryInterval() { + return maxRetryInterval; + } + + public Duration getRetryTimeout() { + return retryTimeout; + } + + public static Builder newBuilder() { + return new Builder(); + } + + public static class Builder { + + private Integer maxNumberOfAttempts; + private Duration firstRetryInterval; + private Double backoffCoefficient = 1.0; + private Duration maxRetryInterval; + private Duration retryTimeout; + + private Builder() { + } + + /** + * Build the WorkflowTaskRetryPolicy. + * @return WorkflowTaskRetryPolicy + */ + public WorkflowTaskRetryPolicy build() { + return new WorkflowTaskRetryPolicy( + this.maxNumberOfAttempts, + this.firstRetryInterval, + this.backoffCoefficient, + this.maxRetryInterval, + this.retryTimeout + ); + } + + /** + * Set the maximum number of attempts to retry the workflow. + * @param maxNumberOfAttempts Maximum number + * @return This builder + */ + public Builder setMaxNumberOfAttempts(int maxNumberOfAttempts) { + if (maxNumberOfAttempts <= 0) { + throw new IllegalArgumentException("The value for maxNumberOfAttempts must be greater than zero."); + } + + this.maxNumberOfAttempts = maxNumberOfAttempts; + + return this; + } + + /** + * Set the interval to wait before the first retry. + * @param firstRetryInterval Interval + * @return This builder + */ + public Builder setFirstRetryInterval(Duration firstRetryInterval) { + if (firstRetryInterval == null) { + throw new IllegalArgumentException("firstRetryInterval cannot be null."); + } + if (firstRetryInterval.isZero() || firstRetryInterval.isNegative()) { + throw new IllegalArgumentException("The value for firstRetryInterval must be greater than zero."); + } + + this.firstRetryInterval = firstRetryInterval; + + return this; + } + + /** + * Set the backoff coefficient. + * @param backoffCoefficient Double value + * @return This builder + */ + public Builder setBackoffCoefficient(double backoffCoefficient) { + if (backoffCoefficient < 1.0) { + throw new IllegalArgumentException("The value for backoffCoefficient must be greater or equal to 1.0."); + } + + this.backoffCoefficient = backoffCoefficient; + + return this; + } + + /** + * Set the maximum interval to wait between retries. + * @param maxRetryInterval Maximum interval + * @return This builder + */ + public Builder setMaxRetryInterval(@Nullable Duration maxRetryInterval) { + if (maxRetryInterval != null && maxRetryInterval.compareTo(this.firstRetryInterval) < 0) { + throw new IllegalArgumentException( + "The value for maxRetryInterval must be greater than or equal to the value for firstRetryInterval."); + } + + this.maxRetryInterval = maxRetryInterval; + + return this; + } + + /** + * Set the maximum retry timeout. + * @param retryTimeout Maximum retry timeout + * @return This builder + */ + public Builder setRetryTimeout(Duration retryTimeout) { + if (retryTimeout != null && retryTimeout.compareTo(this.firstRetryInterval) < 0) { + throw new IllegalArgumentException( + "The value for retryTimeout must be greater than or equal to the value for firstRetryInterval."); + } + + this.retryTimeout = retryTimeout; + + return this; + } + } + +} diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/client/DaprWorkflowClient.java b/sdk-workflows/src/main/java/io/dapr/workflows/client/DaprWorkflowClient.java index 0b4c387aa3..cc1c0f9b1a 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/client/DaprWorkflowClient.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/client/DaprWorkflowClient.java @@ -17,19 +17,12 @@ import com.microsoft.durabletask.DurableTaskGrpcClientBuilder; import com.microsoft.durabletask.OrchestrationMetadata; import com.microsoft.durabletask.PurgeResult; -import io.dapr.client.Headers; import io.dapr.config.Properties; import io.dapr.utils.NetworkUtils; import io.dapr.workflows.Workflow; import io.dapr.workflows.internal.ApiTokenClientInterceptor; -import io.grpc.CallOptions; -import io.grpc.Channel; -import io.grpc.ClientCall; import io.grpc.ClientInterceptor; -import io.grpc.ForwardingClientCall; import io.grpc.ManagedChannel; -import io.grpc.Metadata; -import io.grpc.MethodDescriptor; import javax.annotation.Nullable; @@ -42,6 +35,8 @@ */ public class DaprWorkflowClient implements AutoCloseable { + private static final ClientInterceptor WORKFLOW_INTERCEPTOR = new ApiTokenClientInterceptor(); + private DurableTaskClient innerClient; private ManagedChannel grpcChannel; @@ -137,7 +132,7 @@ public String scheduleNewWorkflow(Class clazz, Object in * @param options the options for the new workflow, including input, instance ID, etc. * @return the instanceId parameter value. */ - public String scheduleNewWorkflow(Class clazz, NewWorkflowOption options) { + public String scheduleNewWorkflow(Class clazz, NewWorkflowOptions options) { return this.innerClient.scheduleNewOrchestrationInstance(clazz.getCanonicalName(), options.getNewOrchestrationInstanceOptions()); } @@ -272,6 +267,6 @@ public void close() throws InterruptedException { } } - private static ClientInterceptor WORKFLOW_INTERCEPTOR = new ApiTokenClientInterceptor(); + } diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/client/NewWorkflowOption.java b/sdk-workflows/src/main/java/io/dapr/workflows/client/NewWorkflowOptions.java similarity index 86% rename from sdk-workflows/src/main/java/io/dapr/workflows/client/NewWorkflowOption.java rename to sdk-workflows/src/main/java/io/dapr/workflows/client/NewWorkflowOptions.java index d802c8f2c3..b83d4945a5 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/client/NewWorkflowOption.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/client/NewWorkflowOptions.java @@ -20,16 +20,16 @@ /** * Options for starting a new instance of a workflow. */ -public class NewWorkflowOption { +public class NewWorkflowOptions { private final NewOrchestrationInstanceOptions newOrchestrationInstanceOptions = new NewOrchestrationInstanceOptions(); /** * Sets the version of the workflow to start. * * @param version the user-defined version of workflow - * @return this {@link NewWorkflowOption} object + * @return this {@link NewWorkflowOptions} object */ - public NewWorkflowOption setVersion(String version) { + public NewWorkflowOptions setVersion(String version) { this.newOrchestrationInstanceOptions.setVersion(version); return this; } @@ -40,9 +40,9 @@ public NewWorkflowOption setVersion(String version) { *

If no instance ID is configured, the workflow will be created with a randomly generated instance ID. * * @param instanceId the ID of the new workflow - * @return this {@link NewWorkflowOption} object + * @return this {@link NewWorkflowOptions} object */ - public NewWorkflowOption setInstanceId(String instanceId) { + public NewWorkflowOptions setInstanceId(String instanceId) { this.newOrchestrationInstanceOptions.setInstanceId(instanceId); return this; } @@ -51,9 +51,9 @@ public NewWorkflowOption setInstanceId(String instanceId) { * Sets the input of the workflow to start. * * @param input the input of the new workflow - * @return this {@link NewWorkflowOption} object + * @return this {@link NewWorkflowOptions} object */ - public NewWorkflowOption setInput(Object input) { + public NewWorkflowOptions setInput(Object input) { this.newOrchestrationInstanceOptions.setInput(input); return this; } @@ -65,9 +65,9 @@ public NewWorkflowOption setInput(Object input) { * to start them at a specific time in the future. * * @param startTime the start time of the new workflow - * @return this {@link NewWorkflowOption} object + * @return this {@link NewWorkflowOptions} object */ - public NewWorkflowOption setStartTime(Instant startTime) { + public NewWorkflowOptions setStartTime(Instant startTime) { this.newOrchestrationInstanceOptions.setStartTime(startTime); return this; } diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DefaultWorkflowContext.java b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DefaultWorkflowContext.java index fea7ea3968..b843819ad6 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DefaultWorkflowContext.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DefaultWorkflowContext.java @@ -14,11 +14,14 @@ package io.dapr.workflows.runtime; import com.microsoft.durabletask.CompositeTaskFailedException; +import com.microsoft.durabletask.RetryPolicy; import com.microsoft.durabletask.Task; import com.microsoft.durabletask.TaskCanceledException; import com.microsoft.durabletask.TaskOptions; import com.microsoft.durabletask.TaskOrchestrationContext; import io.dapr.workflows.WorkflowContext; +import io.dapr.workflows.WorkflowTaskOptions; +import io.dapr.workflows.WorkflowTaskRetryPolicy; import io.dapr.workflows.runtime.saga.DefaultSagaContext; import io.dapr.workflows.saga.Saga; import io.dapr.workflows.saga.SagaContext; @@ -149,7 +152,7 @@ public Task waitForExternalEvent(String name, Duration timeout, Class * before the event is received */ @Override - public Task waitForExternalEvent(String name, Duration timeout) throws TaskCanceledException { + public Task waitForExternalEvent(String name, Duration timeout) throws TaskCanceledException { return this.innerContext.waitForExternalEvent(name, timeout, Void.class); } @@ -165,7 +168,7 @@ public Task waitForExternalEvent(String name, Duration timeout) throws * @return a new {@link Task} that completes when the external event is received */ @Override - public Task waitForExternalEvent(String name) throws TaskCanceledException { + public Task waitForExternalEvent(String name) throws TaskCanceledException { return this.innerContext.waitForExternalEvent(name, null, Void.class); } @@ -177,8 +180,10 @@ public boolean isReplaying() { /** * {@inheritDoc} */ - public Task callActivity(String name, Object input, TaskOptions options, Class returnType) { - return this.innerContext.callActivity(name, input, options, returnType); + public Task callActivity(String name, Object input, WorkflowTaskOptions options, Class returnType) { + TaskOptions taskOptions = toTaskOptions(options); + + return this.innerContext.callActivity(name, input, taskOptions, returnType); } /** @@ -214,9 +219,10 @@ public T getInput(Class targetType) { */ @Override public Task callChildWorkflow(String name, @Nullable Object input, @Nullable String instanceID, - @Nullable TaskOptions options, Class returnType) { + @Nullable WorkflowTaskOptions options, Class returnType) { + TaskOptions taskOptions = toTaskOptions(options); - return this.innerContext.callSubOrchestrator(name, input, instanceID, options, returnType); + return this.innerContext.callSubOrchestrator(name, input, instanceID, taskOptions, returnType); } /** @@ -251,4 +257,21 @@ public SagaContext getSagaContext() { return new DefaultSagaContext(this.saga, this); } + + private static TaskOptions toTaskOptions(WorkflowTaskOptions options) { + if (options == null) { + return null; + } + + WorkflowTaskRetryPolicy workflowTaskRetryPolicy = options.getRetryPolicy(); + RetryPolicy retryPolicy = new RetryPolicy( + workflowTaskRetryPolicy.getMaxNumberOfAttempts(), + workflowTaskRetryPolicy.getFirstRetryInterval() + ); + + retryPolicy.setBackoffCoefficient(workflowTaskRetryPolicy.getBackoffCoefficient()); + retryPolicy.setRetryTimeout(workflowTaskRetryPolicy.getRetryTimeout()); + + return new TaskOptions(retryPolicy); + } } diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/saga/CompensationInformation.java b/sdk-workflows/src/main/java/io/dapr/workflows/saga/CompensationInformation.java index 3e0cb7d413..33a2f741d1 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/saga/CompensationInformation.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/saga/CompensationInformation.java @@ -13,7 +13,7 @@ package io.dapr.workflows.saga; -import com.microsoft.durabletask.TaskOptions; +import io.dapr.workflows.WorkflowTaskOptions; /** * Information for a compensation activity. @@ -21,7 +21,7 @@ class CompensationInformation { private final String compensationActivityClassName; private final Object compensationActivityInput; - private final TaskOptions taskOptions; + private final WorkflowTaskOptions options; /** * Constructor for a compensation information. @@ -30,13 +30,13 @@ class CompensationInformation { * compensation. * @param compensationActivityInput Input of the activity to do * compensation. - * @param taskOptions task options to set retry strategy + * @param options Task options to set retry strategy */ public CompensationInformation(String compensationActivityClassName, - Object compensationActivityInput, TaskOptions taskOptions) { + Object compensationActivityInput, WorkflowTaskOptions options) { this.compensationActivityClassName = compensationActivityClassName; this.compensationActivityInput = compensationActivityInput; - this.taskOptions = taskOptions; + this.options = options; } /** @@ -62,7 +62,7 @@ public Object getCompensationActivityInput() { * * @return task options, null if not set */ - public TaskOptions getTaskOptions() { - return taskOptions; + public WorkflowTaskOptions getExecutionOptions() { + return options; } } diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/saga/Saga.java b/sdk-workflows/src/main/java/io/dapr/workflows/saga/Saga.java index 56fc08f2d0..f02da10b47 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/saga/Saga.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/saga/Saga.java @@ -14,28 +14,28 @@ package io.dapr.workflows.saga; import com.microsoft.durabletask.Task; -import com.microsoft.durabletask.TaskOptions; import com.microsoft.durabletask.interruption.ContinueAsNewInterruption; import com.microsoft.durabletask.interruption.OrchestratorBlockedException; import io.dapr.workflows.WorkflowContext; +import io.dapr.workflows.WorkflowTaskOptions; import java.util.ArrayList; import java.util.List; public final class Saga { - private final SagaOption option; + private final SagaOptions options; private final List compensationActivities = new ArrayList<>(); /** * Build up a Saga with its options. * - * @param option Saga option. + * @param options Saga option. */ - public Saga(SagaOption option) { - if (option == null) { + public Saga(SagaOptions options) { + if (options == null) { throw new IllegalArgumentException("option is required and should not be null."); } - this.option = option; + this.options = options; } /** @@ -50,16 +50,16 @@ public void registerCompensation(String activityClassName, Object activityInput) /** * Register a compensation activity. - * + * * @param activityClassName name of the activity class * @param activityInput input of the activity to be compensated - * @param taskOptions task options to set retry strategy + * @param options task options to set retry strategy */ - public void registerCompensation(String activityClassName, Object activityInput, TaskOptions taskOptions) { + public void registerCompensation(String activityClassName, Object activityInput, WorkflowTaskOptions options) { if (activityClassName == null || activityClassName.isEmpty()) { throw new IllegalArgumentException("activityClassName is required and should not be null or empty."); } - this.compensationActivities.add(new CompensationInformation(activityClassName, activityInput, taskOptions)); + this.compensationActivities.add(new CompensationInformation(activityClassName, activityInput, options)); } /** @@ -72,7 +72,7 @@ public void compensate(WorkflowContext ctx) { // Special case: when parallel compensation is enabled and there is only one // compensation, we still // compensate sequentially. - if (option.isParallelCompensation() && compensationActivities.size() > 1) { + if (options.isParallelCompensation() && compensationActivities.size() > 1) { compensateInParallel(ctx); } else { compensateSequentially(ctx); @@ -109,7 +109,7 @@ private void compensateSequentially(WorkflowContext ctx) { sagaException.addSuppressed(e); } - if (!option.isContinueWithError()) { + if (!options.isContinueWithError()) { throw sagaException; } } @@ -124,6 +124,6 @@ private Task executeCompensateActivity(WorkflowContext ctx, CompensationIn throws SagaCompensationException { String activityClassName = info.getCompensationActivityClassName(); return ctx.callActivity(activityClassName, info.getCompensationActivityInput(), - info.getTaskOptions()); + info.getExecutionOptions()); } } diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/saga/SagaOption.java b/sdk-workflows/src/main/java/io/dapr/workflows/saga/SagaOptions.java similarity index 91% rename from sdk-workflows/src/main/java/io/dapr/workflows/saga/SagaOption.java rename to sdk-workflows/src/main/java/io/dapr/workflows/saga/SagaOptions.java index f3c082f58c..8a7184b6df 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/saga/SagaOption.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/saga/SagaOptions.java @@ -16,12 +16,12 @@ /** * Saga option. */ -public final class SagaOption { +public final class SagaOptions { private final boolean parallelCompensation; private final int maxParallelThread; private final boolean continueWithError; - private SagaOption(boolean parallelCompensation, int maxParallelThread, boolean continueWithError) { + private SagaOptions(boolean parallelCompensation, int maxParallelThread, boolean continueWithError) { this.parallelCompensation = parallelCompensation; this.maxParallelThread = maxParallelThread; this.continueWithError = continueWithError; @@ -95,8 +95,8 @@ public Builder setContinueWithError(boolean continueWithError) { * Build Saga option. * @return Saga option */ - public SagaOption build() { - return new SagaOption(this.parallelCompensation, this.maxParallelThread, this.continueWithError); + public SagaOptions build() { + return new SagaOptions(this.parallelCompensation, this.maxParallelThread, this.continueWithError); } } } diff --git a/sdk-workflows/src/test/java/io/dapr/workflows/DefaultWorkflowContextTest.java b/sdk-workflows/src/test/java/io/dapr/workflows/DefaultWorkflowContextTest.java index abbbae491e..32af9fc6f2 100644 --- a/sdk-workflows/src/test/java/io/dapr/workflows/DefaultWorkflowContextTest.java +++ b/sdk-workflows/src/test/java/io/dapr/workflows/DefaultWorkflowContextTest.java @@ -14,7 +14,6 @@ package io.dapr.workflows; import com.microsoft.durabletask.CompositeTaskFailedException; -import com.microsoft.durabletask.RetryPolicy; import com.microsoft.durabletask.Task; import com.microsoft.durabletask.TaskCanceledException; import com.microsoft.durabletask.TaskOptions; @@ -27,6 +26,7 @@ import org.jetbrains.annotations.Nullable; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; import org.slf4j.Logger; import java.time.Duration; @@ -35,9 +35,10 @@ import java.util.Arrays; import java.util.List; -import static org.junit.Assert.assertNotNull; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -86,17 +87,17 @@ public Task waitForExternalEvent(String name, Duration timeout, Class } @Override - public Task waitForExternalEvent(String name, Duration timeout) throws TaskCanceledException { + public Task waitForExternalEvent(String name, Duration timeout) throws TaskCanceledException { return null; } @Override - public Task waitForExternalEvent(String name) throws TaskCanceledException { + public Task waitForExternalEvent(String name) throws TaskCanceledException { return null; } @Override - public Task callActivity(String name, Object input, TaskOptions options, Class returnType) { + public Task callActivity(String name, Object input, WorkflowTaskOptions options, Class returnType) { return null; } @@ -127,7 +128,7 @@ public V getInput(Class targetType) { @Override public Task callChildWorkflow(String name, @Nullable Object input, @Nullable String instanceID, - @Nullable TaskOptions options, Class returnType) { + @Nullable WorkflowTaskOptions options, Class returnType) { return null; } @@ -190,15 +191,12 @@ public void callActivityTest() { @Test public void DaprWorkflowContextWithEmptyInnerContext() { - assertThrows(IllegalArgumentException.class, () -> { - context = new DefaultWorkflowContext(mockInnerContext, (Logger)null); - }); } + assertThrows(IllegalArgumentException.class, () -> + context = new DefaultWorkflowContext(mockInnerContext, (Logger)null)); } @Test public void DaprWorkflowContextWithEmptyLogger() { - assertThrows(IllegalArgumentException.class, () -> { - context = new DefaultWorkflowContext(null, (Logger)null); - }); + assertThrows(IllegalArgumentException.class, () -> context = new DefaultWorkflowContext(null, (Logger)null)); } @Test @@ -291,11 +289,28 @@ public void callChildWorkflowWithOptions() { String expectedName = "TestActivity"; String expectedInput = "TestInput"; String expectedInstanceId = "TestInstanceId"; - TaskOptions expectedOptions = new TaskOptions(new RetryPolicy(1, Duration.ofSeconds(10))); - - context.callChildWorkflow(expectedName, expectedInput, expectedInstanceId, expectedOptions, String.class); - verify(mockInnerContext, times(1)).callSubOrchestrator(expectedName, expectedInput, expectedInstanceId, - expectedOptions, String.class); + WorkflowTaskRetryPolicy retryPolicy = WorkflowTaskRetryPolicy.newBuilder() + .setMaxNumberOfAttempts(1) + .setFirstRetryInterval(Duration.ofSeconds(10)) + .build(); + WorkflowTaskOptions executionOptions = new WorkflowTaskOptions(retryPolicy); + ArgumentCaptor captor = ArgumentCaptor.forClass(TaskOptions.class); + + context.callChildWorkflow(expectedName, expectedInput, expectedInstanceId, executionOptions, String.class); + + verify(mockInnerContext, times(1)) + .callSubOrchestrator( + eq(expectedName), + eq(expectedInput), + eq(expectedInstanceId), + captor.capture(), + eq(String.class) + ); + + TaskOptions taskOptions = captor.getValue(); + + assertEquals(retryPolicy.getMaxNumberOfAttempts(), taskOptions.getRetryPolicy().getMaxNumberOfAttempts()); + assertEquals(retryPolicy.getFirstRetryInterval(), taskOptions.getRetryPolicy().getFirstRetryInterval()); } @Test @@ -326,14 +341,12 @@ public void getSagaContextTest_sagaEnabled() { WorkflowContext context = new DefaultWorkflowContext(mockInnerContext, saga); SagaContext sagaContext = context.getSagaContext(); - assertNotNull("SagaContext should not be null", sagaContext); + assertNotNull(sagaContext, "SagaContext should not be null"); } @Test public void getSagaContextTest_sagaDisabled() { WorkflowContext context = new DefaultWorkflowContext(mockInnerContext); - assertThrows(UnsupportedOperationException.class, () -> { - context.getSagaContext(); - }); + assertThrows(UnsupportedOperationException.class, context::getSagaContext); } } diff --git a/sdk-workflows/src/test/java/io/dapr/workflows/WorkflowTest.java b/sdk-workflows/src/test/java/io/dapr/workflows/WorkflowTest.java index 439827d3c1..f319709eca 100644 --- a/sdk-workflows/src/test/java/io/dapr/workflows/WorkflowTest.java +++ b/sdk-workflows/src/test/java/io/dapr/workflows/WorkflowTest.java @@ -22,7 +22,7 @@ import io.dapr.workflows.saga.SagaCompensationException; import io.dapr.workflows.saga.SagaContext; -import io.dapr.workflows.saga.SagaOption; +import io.dapr.workflows.saga.SagaOptions; public class WorkflowTest { @@ -188,8 +188,8 @@ public WorkflowStub create() { } @Override - public SagaOption getSagaOption() { - return SagaOption.newBuilder() + public SagaOptions getSagaOption() { + return SagaOptions.newBuilder() .setParallelCompensation(false) .build(); } diff --git a/sdk-workflows/src/test/java/io/dapr/workflows/client/DaprWorkflowClientTest.java b/sdk-workflows/src/test/java/io/dapr/workflows/client/DaprWorkflowClientTest.java index cf366e6b8d..fdb0534181 100644 --- a/sdk-workflows/src/test/java/io/dapr/workflows/client/DaprWorkflowClientTest.java +++ b/sdk-workflows/src/test/java/io/dapr/workflows/client/DaprWorkflowClientTest.java @@ -111,13 +111,13 @@ public void scheduleNewWorkflowWithArgsNameInputInstance() { public void scheduleNewWorkflowWithNewWorkflowOption() { String expectedName = TestWorkflow.class.getCanonicalName(); Object expectedInput = new Object(); - NewWorkflowOption newWorkflowOption = new NewWorkflowOption(); - newWorkflowOption.setInput(expectedInput).setStartTime(Instant.now()); + NewWorkflowOptions newWorkflowOptions = new NewWorkflowOptions(); + newWorkflowOptions.setInput(expectedInput).setStartTime(Instant.now()); - client.scheduleNewWorkflow(TestWorkflow.class, newWorkflowOption); + client.scheduleNewWorkflow(TestWorkflow.class, newWorkflowOptions); verify(mockInnerClient, times(1)) - .scheduleNewOrchestrationInstance(expectedName, newWorkflowOption.getNewOrchestrationInstanceOptions()); + .scheduleNewOrchestrationInstance(expectedName, newWorkflowOptions.getNewOrchestrationInstanceOptions()); } @Test diff --git a/sdk-workflows/src/test/java/io/dapr/workflows/client/NewWorkflowOptionTest.java b/sdk-workflows/src/test/java/io/dapr/workflows/client/NewWorkflowOptionsTest.java similarity index 87% rename from sdk-workflows/src/test/java/io/dapr/workflows/client/NewWorkflowOptionTest.java rename to sdk-workflows/src/test/java/io/dapr/workflows/client/NewWorkflowOptionsTest.java index 78feb84f26..e1d10c68ce 100644 --- a/sdk-workflows/src/test/java/io/dapr/workflows/client/NewWorkflowOptionTest.java +++ b/sdk-workflows/src/test/java/io/dapr/workflows/client/NewWorkflowOptionsTest.java @@ -5,11 +5,11 @@ import java.time.Instant; -public class NewWorkflowOptionTest { +public class NewWorkflowOptionsTest { @Test void testNewWorkflowOption() { - NewWorkflowOption workflowOption = new NewWorkflowOption(); + NewWorkflowOptions workflowOption = new NewWorkflowOptions(); String version = "v1"; String instanceId = "123"; Object input = new Object(); diff --git a/sdk-workflows/src/test/java/io/dapr/workflows/saga/SagaIntegrationTest.java b/sdk-workflows/src/test/java/io/dapr/workflows/saga/SagaIntegrationTest.java index 99342bbde1..0838aa1a3d 100644 --- a/sdk-workflows/src/test/java/io/dapr/workflows/saga/SagaIntegrationTest.java +++ b/sdk-workflows/src/test/java/io/dapr/workflows/saga/SagaIntegrationTest.java @@ -51,7 +51,7 @@ public void testSaga_compensateInParallel() { } private boolean doExecuteWorkflowWithSaga(boolean parallelCompensation) { - SagaOption config = SagaOption.newBuilder() + SagaOptions config = SagaOptions.newBuilder() .setParallelCompensation(parallelCompensation) .setContinueWithError(true).build(); Saga saga = new Saga(config); diff --git a/sdk-workflows/src/test/java/io/dapr/workflows/saga/SagaOptionTest.java b/sdk-workflows/src/test/java/io/dapr/workflows/saga/SagaOptionsTest.java similarity index 78% rename from sdk-workflows/src/test/java/io/dapr/workflows/saga/SagaOptionTest.java rename to sdk-workflows/src/test/java/io/dapr/workflows/saga/SagaOptionsTest.java index 996f199dce..76c5388138 100644 --- a/sdk-workflows/src/test/java/io/dapr/workflows/saga/SagaOptionTest.java +++ b/sdk-workflows/src/test/java/io/dapr/workflows/saga/SagaOptionsTest.java @@ -5,15 +5,15 @@ import org.junit.Test; -public class SagaOptionTest { +public class SagaOptionsTest { @Test public void testBuild() { - SagaOption.Builder builder = SagaOption.newBuilder(); + SagaOptions.Builder builder = SagaOptions.newBuilder(); builder.setParallelCompensation(true); builder.setMaxParallelThread(32); builder.setContinueWithError(false); - SagaOption option = builder.build(); + SagaOptions option = builder.build(); assertEquals(true, option.isParallelCompensation()); assertEquals(32, option.getMaxParallelThread()); @@ -22,8 +22,8 @@ public void testBuild() { @Test public void testBuild_default() { - SagaOption.Builder builder = SagaOption.newBuilder(); - SagaOption option = builder.build(); + SagaOptions.Builder builder = SagaOptions.newBuilder(); + SagaOptions option = builder.build(); assertEquals(false, option.isParallelCompensation()); assertEquals(16, option.getMaxParallelThread()); @@ -32,7 +32,7 @@ public void testBuild_default() { @Test public void testsetMaxParallelThread() { - SagaOption.Builder builder = SagaOption.newBuilder(); + SagaOptions.Builder builder = SagaOptions.newBuilder(); assertThrows(IllegalArgumentException.class, () -> { builder.setMaxParallelThread(0); diff --git a/sdk-workflows/src/test/java/io/dapr/workflows/saga/SagaTest.java b/sdk-workflows/src/test/java/io/dapr/workflows/saga/SagaTest.java index 72df912025..8afa2eb10f 100644 --- a/sdk-workflows/src/test/java/io/dapr/workflows/saga/SagaTest.java +++ b/sdk-workflows/src/test/java/io/dapr/workflows/saga/SagaTest.java @@ -33,13 +33,13 @@ import java.util.concurrent.TimeUnit; import io.dapr.workflows.WorkflowActivityContext; +import io.dapr.workflows.WorkflowTaskOptions; import org.junit.Test; import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import com.microsoft.durabletask.Task; -import com.microsoft.durabletask.TaskOptions; import io.dapr.workflows.WorkflowContext; import io.dapr.workflows.WorkflowActivity; @@ -48,7 +48,7 @@ public class SagaTest { public static WorkflowContext createMockContext() { WorkflowContext workflowContext = mock(WorkflowContext.class); - when(workflowContext.callActivity(anyString(), any(), eq((TaskOptions) null))).thenAnswer(new ActivityAnswer()); + when(workflowContext.callActivity(anyString(), any(), eq((WorkflowTaskOptions) null))).thenAnswer(new ActivityAnswer()); when(workflowContext.allOf(anyList())).thenAnswer(new AllActivityAnswer()); return workflowContext; @@ -63,7 +63,7 @@ public void testSaga_IllegalArgument() { @Test public void testregisterCompensation() { - SagaOption config = SagaOption.newBuilder() + SagaOptions config = SagaOptions.newBuilder() .setParallelCompensation(false) .setContinueWithError(true).build(); Saga saga = new Saga(config); @@ -73,7 +73,7 @@ public void testregisterCompensation() { @Test public void testregisterCompensation_IllegalArgument() { - SagaOption config = SagaOption.newBuilder() + SagaOptions config = SagaOptions.newBuilder() .setParallelCompensation(false) .setContinueWithError(true).build(); Saga saga = new Saga(config); @@ -88,43 +88,43 @@ public void testregisterCompensation_IllegalArgument() { @Test public void testCompensateInParallel() { - MockCompentationActivity.compensateOrder.clear(); + MockCompensationActivity.compensateOrder.clear(); - SagaOption config = SagaOption.newBuilder() + SagaOptions config = SagaOptions.newBuilder() .setParallelCompensation(true).build(); Saga saga = new Saga(config); MockActivityInput input1 = new MockActivityInput(); input1.setOrder(1); - saga.registerCompensation(MockCompentationActivity.class.getName(), input1); + saga.registerCompensation(MockCompensationActivity.class.getName(), input1); MockActivityInput input2 = new MockActivityInput(); input2.setOrder(2); - saga.registerCompensation(MockCompentationActivity.class.getName(), input2); + saga.registerCompensation(MockCompensationActivity.class.getName(), input2); MockActivityInput input3 = new MockActivityInput(); input3.setOrder(3); - saga.registerCompensation(MockCompentationActivity.class.getName(), input3); + saga.registerCompensation(MockCompensationActivity.class.getName(), input3); saga.compensate(createMockContext()); - assertEquals(3, MockCompentationActivity.compensateOrder.size()); + assertEquals(3, MockCompensationActivity.compensateOrder.size()); } @Test public void testCompensateInParallel_exception_1failed() { - MockCompentationActivity.compensateOrder.clear(); + MockCompensationActivity.compensateOrder.clear(); - SagaOption config = SagaOption.newBuilder() + SagaOptions config = SagaOptions.newBuilder() .setParallelCompensation(true).build(); Saga saga = new Saga(config); MockActivityInput input1 = new MockActivityInput(); input1.setOrder(1); - saga.registerCompensation(MockCompentationActivity.class.getName(), input1); + saga.registerCompensation(MockCompensationActivity.class.getName(), input1); MockActivityInput input2 = new MockActivityInput(); input2.setOrder(2); input2.setThrowException(true); - saga.registerCompensation(MockCompentationActivity.class.getName(), input2); + saga.registerCompensation(MockCompensationActivity.class.getName(), input2); MockActivityInput input3 = new MockActivityInput(); input3.setOrder(3); - saga.registerCompensation(MockCompentationActivity.class.getName(), input3); + saga.registerCompensation(MockCompensationActivity.class.getName(), input3); SagaCompensationException exception = assertThrows(SagaCompensationException.class, () -> { saga.compensate(createMockContext()); @@ -132,110 +132,110 @@ public void testCompensateInParallel_exception_1failed() { assertNotNull(exception.getCause()); // 3 compentation activities, 2 succeed, 1 failed assertEquals(0, exception.getSuppressed().length); - assertEquals(2, MockCompentationActivity.compensateOrder.size()); + assertEquals(2, MockCompensationActivity.compensateOrder.size()); } @Test public void testCompensateInParallel_exception_2failed() { - MockCompentationActivity.compensateOrder.clear(); + MockCompensationActivity.compensateOrder.clear(); - SagaOption config = SagaOption.newBuilder() + SagaOptions config = SagaOptions.newBuilder() .setParallelCompensation(true).build(); Saga saga = new Saga(config); MockActivityInput input1 = new MockActivityInput(); input1.setOrder(1); - saga.registerCompensation(MockCompentationActivity.class.getName(), input1); + saga.registerCompensation(MockCompensationActivity.class.getName(), input1); MockActivityInput input2 = new MockActivityInput(); input2.setOrder(2); input2.setThrowException(true); - saga.registerCompensation(MockCompentationActivity.class.getName(), input2); + saga.registerCompensation(MockCompensationActivity.class.getName(), input2); MockActivityInput input3 = new MockActivityInput(); input3.setOrder(3); input3.setThrowException(true); - saga.registerCompensation(MockCompentationActivity.class.getName(), input3); + saga.registerCompensation(MockCompensationActivity.class.getName(), input3); SagaCompensationException exception = assertThrows(SagaCompensationException.class, () -> { saga.compensate(createMockContext()); }); assertNotNull(exception.getCause()); // 3 compentation activities, 1 succeed, 2 failed - assertEquals(1, MockCompentationActivity.compensateOrder.size()); + assertEquals(1, MockCompensationActivity.compensateOrder.size()); } @Test public void testCompensateInParallel_exception_3failed() { - MockCompentationActivity.compensateOrder.clear(); + MockCompensationActivity.compensateOrder.clear(); - SagaOption config = SagaOption.newBuilder() + SagaOptions config = SagaOptions.newBuilder() .setParallelCompensation(true).build(); Saga saga = new Saga(config); MockActivityInput input1 = new MockActivityInput(); input1.setOrder(1); input1.setThrowException(true); - saga.registerCompensation(MockCompentationActivity.class.getName(), input1); + saga.registerCompensation(MockCompensationActivity.class.getName(), input1); MockActivityInput input2 = new MockActivityInput(); input2.setOrder(2); input2.setThrowException(true); - saga.registerCompensation(MockCompentationActivity.class.getName(), input2); + saga.registerCompensation(MockCompensationActivity.class.getName(), input2); MockActivityInput input3 = new MockActivityInput(); input3.setOrder(3); input3.setThrowException(true); - saga.registerCompensation(MockCompentationActivity.class.getName(), input3); + saga.registerCompensation(MockCompensationActivity.class.getName(), input3); SagaCompensationException exception = assertThrows(SagaCompensationException.class, () -> { saga.compensate(createMockContext()); }); assertNotNull(exception.getCause()); // 3 compentation activities, 0 succeed, 3 failed - assertEquals(0, MockCompentationActivity.compensateOrder.size()); + assertEquals(0, MockCompensationActivity.compensateOrder.size()); } @Test public void testCompensateSequentially() { - MockCompentationActivity.compensateOrder.clear(); + MockCompensationActivity.compensateOrder.clear(); - SagaOption config = SagaOption.newBuilder() + SagaOptions config = SagaOptions.newBuilder() .setParallelCompensation(false).build(); Saga saga = new Saga(config); MockActivityInput input1 = new MockActivityInput(); input1.setOrder(1); - saga.registerCompensation(MockCompentationActivity.class.getName(), input1); + saga.registerCompensation(MockCompensationActivity.class.getName(), input1); MockActivityInput input2 = new MockActivityInput(); input2.setOrder(2); - saga.registerCompensation(MockCompentationActivity.class.getName(), input2); + saga.registerCompensation(MockCompensationActivity.class.getName(), input2); MockActivityInput input3 = new MockActivityInput(); input3.setOrder(3); - saga.registerCompensation(MockCompentationActivity.class.getName(), input3); + saga.registerCompensation(MockCompensationActivity.class.getName(), input3); saga.compensate(createMockContext()); - assertEquals(3, MockCompentationActivity.compensateOrder.size()); + assertEquals(3, MockCompensationActivity.compensateOrder.size()); // the order should be 3 / 2 / 1 - assertEquals(Integer.valueOf(3), MockCompentationActivity.compensateOrder.get(0)); - assertEquals(Integer.valueOf(2), MockCompentationActivity.compensateOrder.get(1)); - assertEquals(Integer.valueOf(1), MockCompentationActivity.compensateOrder.get(2)); + assertEquals(Integer.valueOf(3), MockCompensationActivity.compensateOrder.get(0)); + assertEquals(Integer.valueOf(2), MockCompensationActivity.compensateOrder.get(1)); + assertEquals(Integer.valueOf(1), MockCompensationActivity.compensateOrder.get(2)); } @Test public void testCompensateSequentially_continueWithError() { - MockCompentationActivity.compensateOrder.clear(); + MockCompensationActivity.compensateOrder.clear(); - SagaOption config = SagaOption.newBuilder() + SagaOptions config = SagaOptions.newBuilder() .setParallelCompensation(false) .setContinueWithError(true) .build(); Saga saga = new Saga(config); MockActivityInput input1 = new MockActivityInput(); input1.setOrder(1); - saga.registerCompensation(MockCompentationActivity.class.getName(), input1); + saga.registerCompensation(MockCompensationActivity.class.getName(), input1); MockActivityInput input2 = new MockActivityInput(); input2.setOrder(2); input2.setThrowException(true); - saga.registerCompensation(MockCompentationActivity.class.getName(), input2); + saga.registerCompensation(MockCompensationActivity.class.getName(), input2); MockActivityInput input3 = new MockActivityInput(); input3.setOrder(3); - saga.registerCompensation(MockCompentationActivity.class.getName(), input3); + saga.registerCompensation(MockCompensationActivity.class.getName(), input3); SagaCompensationException exception = assertThrows(SagaCompensationException.class, () -> { saga.compensate(createMockContext()); @@ -244,32 +244,32 @@ public void testCompensateSequentially_continueWithError() { assertEquals(0, exception.getSuppressed().length); // 3 compentation activities, 2 succeed, 1 failed - assertEquals(2, MockCompentationActivity.compensateOrder.size()); + assertEquals(2, MockCompensationActivity.compensateOrder.size()); // the order should be 3 / 1 - assertEquals(Integer.valueOf(3), MockCompentationActivity.compensateOrder.get(0)); - assertEquals(Integer.valueOf(1), MockCompentationActivity.compensateOrder.get(1)); + assertEquals(Integer.valueOf(3), MockCompensationActivity.compensateOrder.get(0)); + assertEquals(Integer.valueOf(1), MockCompensationActivity.compensateOrder.get(1)); } @Test public void testCompensateSequentially_continueWithError_suppressed() { - MockCompentationActivity.compensateOrder.clear(); + MockCompensationActivity.compensateOrder.clear(); - SagaOption config = SagaOption.newBuilder() + SagaOptions config = SagaOptions.newBuilder() .setParallelCompensation(false) .setContinueWithError(true) .build(); Saga saga = new Saga(config); MockActivityInput input1 = new MockActivityInput(); input1.setOrder(1); - saga.registerCompensation(MockCompentationActivity.class.getName(), input1); + saga.registerCompensation(MockCompensationActivity.class.getName(), input1); MockActivityInput input2 = new MockActivityInput(); input2.setOrder(2); input2.setThrowException(true); - saga.registerCompensation(MockCompentationActivity.class.getName(), input2); + saga.registerCompensation(MockCompensationActivity.class.getName(), input2); MockActivityInput input3 = new MockActivityInput(); input3.setOrder(3); input3.setThrowException(true); - saga.registerCompensation(MockCompentationActivity.class.getName(), input3); + saga.registerCompensation(MockCompensationActivity.class.getName(), input3); SagaCompensationException exception = assertThrows(SagaCompensationException.class, () -> { saga.compensate(createMockContext()); @@ -278,30 +278,30 @@ public void testCompensateSequentially_continueWithError_suppressed() { assertEquals(1, exception.getSuppressed().length); // 3 compentation activities, 1 succeed, 2 failed - assertEquals(1, MockCompentationActivity.compensateOrder.size()); + assertEquals(1, MockCompensationActivity.compensateOrder.size()); // the order should be 3 / 1 - assertEquals(Integer.valueOf(1), MockCompentationActivity.compensateOrder.get(0)); + assertEquals(Integer.valueOf(1), MockCompensationActivity.compensateOrder.get(0)); } @Test public void testCompensateSequentially_notContinueWithError() { - MockCompentationActivity.compensateOrder.clear(); + MockCompensationActivity.compensateOrder.clear(); - SagaOption config = SagaOption.newBuilder() + SagaOptions config = SagaOptions.newBuilder() .setParallelCompensation(false) .setContinueWithError(false) .build(); Saga saga = new Saga(config); MockActivityInput input1 = new MockActivityInput(); input1.setOrder(1); - saga.registerCompensation(MockCompentationActivity.class.getName(), input1); + saga.registerCompensation(MockCompensationActivity.class.getName(), input1); MockActivityInput input2 = new MockActivityInput(); input2.setOrder(2); input2.setThrowException(true); - saga.registerCompensation(MockCompentationActivity.class.getName(), input2); + saga.registerCompensation(MockCompensationActivity.class.getName(), input2); MockActivityInput input3 = new MockActivityInput(); input3.setOrder(3); - saga.registerCompensation(MockCompentationActivity.class.getName(), input3); + saga.registerCompensation(MockCompensationActivity.class.getName(), input3); SagaCompensationException exception = assertThrows(SagaCompensationException.class, () -> { saga.compensate(createMockContext()); @@ -310,9 +310,9 @@ public void testCompensateSequentially_notContinueWithError() { assertEquals(0, exception.getSuppressed().length); // 3 compentation activities, 1 succeed, 1 failed and not continue - assertEquals(1, MockCompentationActivity.compensateOrder.size()); + assertEquals(1, MockCompensationActivity.compensateOrder.size()); // the order should be 3 / 1 - assertEquals(Integer.valueOf(3), MockCompentationActivity.compensateOrder.get(0)); + assertEquals(Integer.valueOf(3), MockCompensationActivity.compensateOrder.get(0)); } public static class MockActivity implements WorkflowActivity { @@ -325,9 +325,9 @@ public Object run(WorkflowActivityContext ctx) { } } - public static class MockCompentationActivity implements WorkflowActivity { + public static class MockCompensationActivity implements WorkflowActivity { - private static List compensateOrder = Collections.synchronizedList(new ArrayList<>()); + private static final List compensateOrder = Collections.synchronizedList(new ArrayList<>()); @Override public Object run(WorkflowActivityContext ctx) { From f0468fc6c5f8681ac6ad2d74cc45f0d285ac3ee7 Mon Sep 17 00:00:00 2001 From: Christian Kaps Date: Wed, 5 Feb 2025 07:26:39 +0100 Subject: [PATCH 08/21] Fix formatting issues Signed-off-by: Christian Kaps --- .../dapr/it/testcontainers/DaprActorsIT.java | 14 ++++++------ .../TestDaprActorsConfiguration.java | 22 +++++++++---------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprActorsIT.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprActorsIT.java index 23dc6040fb..a8ed503e73 100644 --- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprActorsIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprActorsIT.java @@ -60,15 +60,15 @@ static void daprProperties(DynamicPropertyRegistry registry) { @Autowired private ActorClient daprActorClient; - @Autowired - private ActorRuntime daprActorRuntime; + @Autowired + private ActorRuntime daprActorRuntime; - @BeforeEach - public void setUp(){ - daprActorRuntime.registerActor(TestActorImpl.class); - } + @BeforeEach + public void setUp(){ + daprActorRuntime.registerActor(TestActorImpl.class); + } - @Test + @Test public void testActors() throws Exception { ActorProxyBuilder builder = new ActorProxyBuilder<>(TestActor.class, daprActorClient); ActorId actorId = ActorId.createRandom(); diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestDaprActorsConfiguration.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestDaprActorsConfiguration.java index cae0e1fa97..f004e16047 100644 --- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestDaprActorsConfiguration.java +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestDaprActorsConfiguration.java @@ -25,16 +25,16 @@ public ActorClient daprActorClient( return new ActorClient(new Properties(overrides)); } - @Bean - public ActorRuntime daprActorRuntime( - @Value("${dapr.http.endpoint}") String daprHttpEndpoint, - @Value("${dapr.grpc.endpoint}") String daprGrpcEndpoint - ){ - Map overrides = Map.of( - "dapr.http.endpoint", daprHttpEndpoint, - "dapr.grpc.endpoint", daprGrpcEndpoint - ); + @Bean + public ActorRuntime daprActorRuntime( + @Value("${dapr.http.endpoint}") String daprHttpEndpoint, + @Value("${dapr.grpc.endpoint}") String daprGrpcEndpoint + ){ + Map overrides = Map.of( + "dapr.http.endpoint", daprHttpEndpoint, + "dapr.grpc.endpoint", daprGrpcEndpoint + ); - return ActorRuntime.getInstance(new Properties(overrides)); - } + return ActorRuntime.getInstance(new Properties(overrides)); + } } From b5e88b573ccc4e44e2bfd0adaf40894b0eb00f96 Mon Sep 17 00:00:00 2001 From: salaboy Date: Mon, 3 Feb 2025 19:28:48 +0000 Subject: [PATCH 09/21] adding spring boot workflows integration (#1195) Co-authored-by: Cassie Coyle Signed-off-by: Christian Kaps --- .../en/java-sdk-docs/spring-boot/_index.md | 49 ++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/daprdocs/content/en/java-sdk-docs/spring-boot/_index.md b/daprdocs/content/en/java-sdk-docs/spring-boot/_index.md index 204104a7cd..74f988fb4a 100644 --- a/daprdocs/content/en/java-sdk-docs/spring-boot/_index.md +++ b/daprdocs/content/en/java-sdk-docs/spring-boot/_index.md @@ -122,7 +122,7 @@ Besides the previous configuration (`DaprTestContainersConfig`) your tests shoul The Java SDK allows you to interface with all of the [Dapr building blocks]({{< ref building-blocks >}}). But if you want to leverage the Spring and Spring Boot programming model you can use the `dapr-spring-boot-starter` integration. This includes implementations of Spring Data (`KeyValueTemplate` and `CrudRepository`) as well as a `DaprMessagingTemplate` for producing and consuming messages -(similar to [Spring Kafka](https://spring.io/projects/spring-kafka), [Spring Pulsar](https://spring.io/projects/spring-pulsar) and [Spring AMQP for RabbitMQ](https://spring.io/projects/spring-amqp)). +(similar to [Spring Kafka](https://spring.io/projects/spring-kafka), [Spring Pulsar](https://spring.io/projects/spring-pulsar) and [Spring AMQP for RabbitMQ](https://spring.io/projects/spring-amqp)) and Dapr workflows. ## Using Spring Data `CrudRepository` and `KeyValueTemplate` @@ -277,6 +277,53 @@ public static void setup(){ You can check and run the [full example source code here](https://github.com/salaboy/dapr-spring-boot-docs-examples). +## Using Dapr Workflows with Spring Boot + +Following the same approach that we used for Spring Data and Spring Messaging, the `dapr-spring-boot-starter` brings Dapr Workflow integration for Spring Boot users. + +To work with Dapr Workflows you need to define and implement your workflows using code. The Dapr Spring Boot Starter makes your life easier by managing `Workflow`s and `WorkflowActivity`s as Spring beans. + +In order to enable the automatic bean discovery you can annotate your `@SpringBootApplication` with the `@EnableDaprWorkflows` annotation: + +``` +@SpringBootApplication +@EnableDaprWorkflows +public class MySpringBootApplication {} +``` + +By adding this annotation, all the `WorkflowActivity`s will be automatically managed by Spring and registered to the workflow engine. + +By having all `WorkflowActivity`s as managed beans we can use Spring `@Autowired` mechanism to inject any bean that our workflow activity might need to implement its functionality, for example the `@RestTemplate`: + +``` +public class MyWorkflowActivity implements WorkflowActivity { + + @Autowired + private RestTemplate restTemplate; +``` + +You can also `@Autowired` the `DaprWorkflowClient` to create new instances of your workflows. + +``` +@Autowired +private DaprWorkflowClient daprWorkflowClient; +``` + +This enable applications to schedule new workflow instances and raise events. + +``` +String instanceId = daprWorkflowClient.scheduleNewWorkflow(MyWorkflow.class, payload); +``` + +and + +``` +daprWorkflowClient.raiseEvent(instanceId, "MyEvenet", event); +``` + +Check the [Dapr Workflow documentation](https://docs.dapr.io/developing-applications/building-blocks/workflow/workflow-overview/) for more information about how to work with Dapr Workflows. + + ## Next steps Learn more about the [Dapr Java SDK packages available to add to your Java applications](https://dapr.github.io/java-sdk/). From f0707cfdd9641d3d0495050a6a7aa5568ca26258 Mon Sep 17 00:00:00 2001 From: artur-ciocanu Date: Mon, 3 Feb 2025 22:22:07 +0200 Subject: [PATCH 10/21] Register workflows and acitivities using instances along classes (#1201) Signed-off-by: Christian Kaps --- .../config/DaprWorkflowsConfiguration.java | 19 ++++--- ...java => WorkflowActivityClassWrapper.java} | 4 +- .../WorkflowActivityInstanceWrapper.java | 46 +++++++++++++++ ...Wrapper.java => WorkflowClassWrapper.java} | 4 +- .../runtime/WorkflowInstanceWrapper.java | 49 ++++++++++++++++ .../runtime/WorkflowRuntimeBuilder.java | 46 +++++++++++++-- ... => WorkflowActivityClassWrapperTest.java} | 25 +++++---- .../WorkflowActivityInstanceWrapperTest.java | 46 +++++++++++++++ ...est.java => WorkflowClassWrapperTest.java} | 17 +++--- .../runtime/WorkflowInstanceWrapperTest.java | 56 +++++++++++++++++++ .../runtime/WorkflowRuntimeBuilderTest.java | 39 +++++++++---- 11 files changed, 306 insertions(+), 45 deletions(-) rename sdk-workflows/src/main/java/io/dapr/workflows/runtime/{WorkflowActivityWrapper.java => WorkflowActivityClassWrapper.java} (92%) create mode 100644 sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowActivityInstanceWrapper.java rename sdk-workflows/src/main/java/io/dapr/workflows/runtime/{WorkflowWrapper.java => WorkflowClassWrapper.java} (93%) create mode 100644 sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowInstanceWrapper.java rename sdk-workflows/src/test/java/io/dapr/workflows/runtime/{WorkflowActivityWrapperTest.java => WorkflowActivityClassWrapperTest.java} (61%) create mode 100644 sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowActivityInstanceWrapperTest.java rename sdk-workflows/src/test/java/io/dapr/workflows/runtime/{WorkflowWrapperTest.java => WorkflowClassWrapperTest.java} (79%) create mode 100644 sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowInstanceWrapperTest.java 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 index 5547d72f48..8629982a90 100644 --- 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 @@ -17,7 +17,7 @@ public class DaprWorkflowsConfiguration implements ApplicationContextAware { private static final Logger LOGGER = LoggerFactory.getLogger(DaprWorkflowsConfiguration.class); - private WorkflowRuntimeBuilder workflowRuntimeBuilder; + private final WorkflowRuntimeBuilder workflowRuntimeBuilder; public DaprWorkflowsConfiguration(WorkflowRuntimeBuilder workflowRuntimeBuilder) { this.workflowRuntimeBuilder = workflowRuntimeBuilder; @@ -29,16 +29,21 @@ public DaprWorkflowsConfiguration(WorkflowRuntimeBuilder workflowRuntimeBuilder) */ 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()); + + for (Workflow workflow : workflowBeans.values()) { + LOGGER.info("Dapr Workflow: '{}' registered", workflow.getClass().getName()); + + workflowRuntimeBuilder.registerWorkflow(workflow); } Map workflowActivitiesBeans = applicationContext.getBeansOfType(WorkflowActivity.class); - for (WorkflowActivity a : workflowActivitiesBeans.values()) { - LOGGER.info("Dapr Workflow Activity: '{}' registered", a.getClass().getName()); - workflowRuntimeBuilder.registerActivity(a.getClass()); + + for (WorkflowActivity activity : workflowActivitiesBeans.values()) { + LOGGER.info("Dapr Workflow Activity: '{}' registered", activity.getClass().getName()); + + workflowRuntimeBuilder.registerActivity(activity); } try (WorkflowRuntime runtime = workflowRuntimeBuilder.build()) { diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowActivityWrapper.java b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowActivityClassWrapper.java similarity index 92% rename from sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowActivityWrapper.java rename to sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowActivityClassWrapper.java index 18f4eb55de..3dcb8ef6b6 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowActivityWrapper.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowActivityClassWrapper.java @@ -23,7 +23,7 @@ /** * Wrapper for Durable Task Framework task activity factory. */ -public class WorkflowActivityWrapper implements TaskActivityFactory { +public class WorkflowActivityClassWrapper implements TaskActivityFactory { private final Constructor activityConstructor; private final String name; @@ -32,7 +32,7 @@ public class WorkflowActivityWrapper implements Task * * @param clazz Class of the activity to wrap. */ - public WorkflowActivityWrapper(Class clazz) { + public WorkflowActivityClassWrapper(Class clazz) { this.name = clazz.getCanonicalName(); try { this.activityConstructor = clazz.getDeclaredConstructor(); diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowActivityInstanceWrapper.java b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowActivityInstanceWrapper.java new file mode 100644 index 0000000000..17d509924e --- /dev/null +++ b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowActivityInstanceWrapper.java @@ -0,0 +1,46 @@ +/* + * Copyright 2023 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file 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.dapr.workflows.runtime; + +import com.microsoft.durabletask.TaskActivity; +import com.microsoft.durabletask.TaskActivityFactory; +import io.dapr.workflows.WorkflowActivity; + +/** + * Wrapper for Durable Task Framework task activity factory. + */ +public class WorkflowActivityInstanceWrapper implements TaskActivityFactory { + private final T activity; + private final String name; + + /** + * Constructor for WorkflowActivityWrapper. + * + * @param instance Instance of the activity to wrap. + */ + public WorkflowActivityInstanceWrapper(T instance) { + this.name = instance.getClass().getCanonicalName(); + this.activity = instance; + } + + @Override + public String getName() { + return name; + } + + @Override + public TaskActivity create() { + return ctx -> activity.run(new DefaultWorkflowActivityContext(ctx)); + } +} diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowWrapper.java b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowClassWrapper.java similarity index 93% rename from sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowWrapper.java rename to sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowClassWrapper.java index 91f1dd8bce..9c0ed95a6d 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowWrapper.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowClassWrapper.java @@ -24,11 +24,11 @@ /** * Wrapper for Durable Task Framework orchestration factory. */ -class WorkflowWrapper implements TaskOrchestrationFactory { +class WorkflowClassWrapper implements TaskOrchestrationFactory { private final Constructor workflowConstructor; private final String name; - public WorkflowWrapper(Class clazz) { + public WorkflowClassWrapper(Class clazz) { this.name = clazz.getCanonicalName(); try { this.workflowConstructor = clazz.getDeclaredConstructor(); diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowInstanceWrapper.java b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowInstanceWrapper.java new file mode 100644 index 0000000000..bda34d5974 --- /dev/null +++ b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowInstanceWrapper.java @@ -0,0 +1,49 @@ +/* + * Copyright 2023 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file 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.dapr.workflows.runtime; + +import com.microsoft.durabletask.TaskOrchestration; +import com.microsoft.durabletask.TaskOrchestrationFactory; +import io.dapr.workflows.Workflow; +import io.dapr.workflows.saga.Saga; + +/** + * Wrapper for Durable Task Framework orchestration factory. + */ +class WorkflowInstanceWrapper implements TaskOrchestrationFactory { + private final T workflow; + private final String name; + + public WorkflowInstanceWrapper(T instance) { + this.name = instance.getClass().getCanonicalName(); + this.workflow = instance; + } + + @Override + public String getName() { + return name; + } + + @Override + public TaskOrchestration create() { + return ctx -> { + if (workflow.getSagaOption() != null) { + Saga saga = new Saga(workflow.getSagaOption()); + workflow.run(new DefaultWorkflowContext(ctx, saga)); + } else { + workflow.run(new DefaultWorkflowContext(ctx)); + } + }; + } +} diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowRuntimeBuilder.java b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowRuntimeBuilder.java index 86d0cf1e09..397e58b30f 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowRuntimeBuilder.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowRuntimeBuilder.java @@ -92,11 +92,30 @@ public WorkflowRuntime build() { * @return the WorkflowRuntimeBuilder */ public WorkflowRuntimeBuilder registerWorkflow(Class clazz) { - this.builder.addOrchestration(new WorkflowWrapper<>(clazz)); + this.builder.addOrchestration(new WorkflowClassWrapper<>(clazz)); this.workflowSet.add(clazz.getCanonicalName()); this.workflows.add(clazz.getSimpleName()); - this.logger.info("Registered Workflow: " + clazz.getSimpleName()); + this.logger.info("Registered Workflow: {}", clazz.getSimpleName()); + + return this; + } + + /** + * Registers a Workflow object. + * + * @param any Workflow type + * @param instance the workflow instance being registered + * @return the WorkflowRuntimeBuilder + */ + public WorkflowRuntimeBuilder registerWorkflow(T instance) { + Class clazz = (Class) instance.getClass(); + + this.builder.addOrchestration(new WorkflowInstanceWrapper<>(instance)); + this.workflowSet.add(clazz.getCanonicalName()); + this.workflows.add(clazz.getSimpleName()); + + this.logger.info("Registered Workflow: {}", clazz.getSimpleName()); return this; } @@ -109,11 +128,30 @@ public WorkflowRuntimeBuilder registerWorkflow(Class cla * @return the WorkflowRuntimeBuilder */ public WorkflowRuntimeBuilder registerActivity(Class clazz) { - this.builder.addActivity(new WorkflowActivityWrapper<>(clazz)); + this.builder.addActivity(new WorkflowActivityClassWrapper<>(clazz)); + this.activitySet.add(clazz.getCanonicalName()); + this.activities.add(clazz.getSimpleName()); + + this.logger.info("Registered Activity: {}", clazz.getSimpleName()); + + return this; + } + + /** + * Registers an Activity object. + * + * @param any WorkflowActivity type + * @param instance the class instance being registered + * @return the WorkflowRuntimeBuilder + */ + public WorkflowRuntimeBuilder registerActivity(T instance) { + Class clazz = (Class) instance.getClass(); + + this.builder.addActivity(new WorkflowActivityInstanceWrapper<>(instance)); this.activitySet.add(clazz.getCanonicalName()); this.activities.add(clazz.getSimpleName()); - this.logger.info("Registered Activity: " + clazz.getSimpleName()); + this.logger.info("Registered Activity: {}", clazz.getSimpleName()); return this; } diff --git a/sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowActivityWrapperTest.java b/sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowActivityClassWrapperTest.java similarity index 61% rename from sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowActivityWrapperTest.java rename to sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowActivityClassWrapperTest.java index 754c02bd8b..0783176051 100644 --- a/sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowActivityWrapperTest.java +++ b/sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowActivityClassWrapperTest.java @@ -3,16 +3,15 @@ import com.microsoft.durabletask.TaskActivityContext; import io.dapr.workflows.WorkflowActivity; import io.dapr.workflows.WorkflowActivityContext; -import org.junit.Assert; import org.junit.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; - -public class WorkflowActivityWrapperTest { +public class WorkflowActivityClassWrapperTest { public static class TestActivity implements WorkflowActivity { @Override public Object run(WorkflowActivityContext ctx) { @@ -22,24 +21,26 @@ public Object run(WorkflowActivityContext ctx) { } @Test - public void getName() throws NoSuchMethodException { - WorkflowActivityWrapper wrapper = new WorkflowActivityWrapper<>( - WorkflowActivityWrapperTest.TestActivity.class); - Assert.assertEquals( - "io.dapr.workflows.runtime.WorkflowActivityWrapperTest.TestActivity", + public void getName() { + WorkflowActivityClassWrapper wrapper = new WorkflowActivityClassWrapper<>(TestActivity.class); + + assertEquals( + "io.dapr.workflows.runtime.WorkflowActivityClassWrapperTest.TestActivity", wrapper.getName() ); } @Test - public void createWithClass() throws NoSuchMethodException { + public void createWithClass() { TaskActivityContext mockContext = mock(TaskActivityContext.class); - WorkflowActivityWrapper wrapper = new WorkflowActivityWrapper<>( - WorkflowActivityWrapperTest.TestActivity.class); + WorkflowActivityClassWrapper wrapper = new WorkflowActivityClassWrapper<>(TestActivity.class); + when(mockContext.getInput(String.class)).thenReturn("Hello"); when(mockContext.getName()).thenReturn("TestActivityContext"); + Object result = wrapper.create().run(mockContext); + verify(mockContext, times(1)).getInput(String.class); - Assert.assertEquals("Hello world! from TestActivityContext", result); + assertEquals("Hello world! from TestActivityContext", result); } } diff --git a/sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowActivityInstanceWrapperTest.java b/sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowActivityInstanceWrapperTest.java new file mode 100644 index 0000000000..bd8788bbdd --- /dev/null +++ b/sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowActivityInstanceWrapperTest.java @@ -0,0 +1,46 @@ +package io.dapr.workflows.runtime; + +import com.microsoft.durabletask.TaskActivityContext; +import io.dapr.workflows.WorkflowActivity; +import io.dapr.workflows.WorkflowActivityContext; +import org.junit.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class WorkflowActivityInstanceWrapperTest { + public static class TestActivity implements WorkflowActivity { + @Override + public Object run(WorkflowActivityContext ctx) { + String activityContextName = ctx.getName(); + return ctx.getInput(String.class) + " world! from " + activityContextName; + } + } + + @Test + public void getName() { + WorkflowActivityInstanceWrapper wrapper = new WorkflowActivityInstanceWrapper<>(new TestActivity()); + + assertEquals( + "io.dapr.workflows.runtime.WorkflowActivityInstanceWrapperTest.TestActivity", + wrapper.getName() + ); + } + + @Test + public void createWithInstance() { + TaskActivityContext mockContext = mock(TaskActivityContext.class); + WorkflowActivityInstanceWrapper wrapper = new WorkflowActivityInstanceWrapper<>(new TestActivity()); + + when(mockContext.getInput(String.class)).thenReturn("Hello"); + when(mockContext.getName()).thenReturn("TestActivityContext"); + + Object result = wrapper.create().run(mockContext); + + verify(mockContext, times(1)).getInput(String.class); + assertEquals("Hello world! from TestActivityContext", result); + } +} diff --git a/sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowWrapperTest.java b/sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowClassWrapperTest.java similarity index 79% rename from sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowWrapperTest.java rename to sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowClassWrapperTest.java index 6066a7f7c0..a73b616bc2 100644 --- a/sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowWrapperTest.java +++ b/sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowClassWrapperTest.java @@ -13,20 +13,19 @@ package io.dapr.workflows.runtime; - import com.microsoft.durabletask.TaskOrchestrationContext; import io.dapr.workflows.Workflow; import io.dapr.workflows.WorkflowContext; import io.dapr.workflows.WorkflowStub; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -public class WorkflowWrapperTest { +public class WorkflowClassWrapperTest { public static class TestWorkflow implements Workflow { @Override public WorkflowStub create() { @@ -36,9 +35,10 @@ public WorkflowStub create() { @Test public void getName() { - WorkflowWrapper wrapper = new WorkflowWrapper<>(TestWorkflow.class); - Assertions.assertEquals( - "io.dapr.workflows.runtime.WorkflowWrapperTest.TestWorkflow", + WorkflowClassWrapper wrapper = new WorkflowClassWrapper<>(TestWorkflow.class); + + assertEquals( + "io.dapr.workflows.runtime.WorkflowClassWrapperTest.TestWorkflow", wrapper.getName() ); } @@ -46,10 +46,11 @@ public void getName() { @Test public void createWithClass() { TaskOrchestrationContext mockContext = mock(TaskOrchestrationContext.class); - WorkflowWrapper wrapper = new WorkflowWrapper<>(TestWorkflow.class); + WorkflowClassWrapper wrapper = new WorkflowClassWrapper<>(TestWorkflow.class); + when(mockContext.getInstanceId()).thenReturn("uuid"); wrapper.create().run(mockContext); verify(mockContext, times(1)).getInstanceId(); } -} \ No newline at end of file +} diff --git a/sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowInstanceWrapperTest.java b/sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowInstanceWrapperTest.java new file mode 100644 index 0000000000..22f315aa53 --- /dev/null +++ b/sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowInstanceWrapperTest.java @@ -0,0 +1,56 @@ +/* + * Copyright 2023 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file 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.dapr.workflows.runtime; + +import com.microsoft.durabletask.TaskOrchestrationContext; +import io.dapr.workflows.Workflow; +import io.dapr.workflows.WorkflowContext; +import io.dapr.workflows.WorkflowStub; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class WorkflowInstanceWrapperTest { + public static class TestWorkflow implements Workflow { + @Override + public WorkflowStub create() { + return WorkflowContext::getInstanceId; + } + } + + @Test + public void getName() { + WorkflowInstanceWrapper wrapper = new WorkflowInstanceWrapper<>(new TestWorkflow()); + + assertEquals( + "io.dapr.workflows.runtime.WorkflowInstanceWrapperTest.TestWorkflow", + wrapper.getName() + ); + } + + @Test + public void createWithInstance() { + TaskOrchestrationContext mockContext = mock(TaskOrchestrationContext.class); + WorkflowInstanceWrapper wrapper = new WorkflowInstanceWrapper<>(new TestWorkflow()); + + when(mockContext.getInstanceId()).thenReturn("uuid"); + wrapper.create().run(mockContext); + verify(mockContext, times(1)).getInstanceId(); + } + +} diff --git a/sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowRuntimeBuilderTest.java b/sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowRuntimeBuilderTest.java index 81e3c30f18..c159930b91 100644 --- a/sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowRuntimeBuilderTest.java +++ b/sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowRuntimeBuilderTest.java @@ -12,16 +12,18 @@ */ package io.dapr.workflows.runtime; - import io.dapr.workflows.Workflow; import io.dapr.workflows.WorkflowActivity; import io.dapr.workflows.WorkflowActivityContext; import io.dapr.workflows.WorkflowStub; import org.junit.jupiter.api.Test; -import org.mockito.Mockito; import org.slf4j.Logger; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import java.io.ByteArrayOutputStream; import java.io.PrintStream; @@ -47,14 +49,30 @@ public void registerValidWorkflowClass() { assertDoesNotThrow(() -> new WorkflowRuntimeBuilder().registerWorkflow(TestWorkflow.class)); } + @Test + public void registerValidWorkflowInstance() { + assertDoesNotThrow(() -> new WorkflowRuntimeBuilder().registerWorkflow(new TestWorkflow())); + } + @Test public void registerValidWorkflowActivityClass() { assertDoesNotThrow(() -> new WorkflowRuntimeBuilder().registerActivity(TestActivity.class)); } + @Test + public void registerValidWorkflowActivityInstance() { + assertDoesNotThrow(() -> new WorkflowRuntimeBuilder().registerActivity(new TestActivity())); + } + @Test public void buildTest() { - assertDoesNotThrow(() -> new WorkflowRuntimeBuilder().build()); + assertDoesNotThrow(() -> { + try (WorkflowRuntime runtime = new WorkflowRuntimeBuilder().build()) { + System.out.println("WorkflowRuntime created"); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); } @Test @@ -63,19 +81,20 @@ public void loggingOutputTest() { ByteArrayOutputStream outStreamCapture = new ByteArrayOutputStream(); System.setOut(new PrintStream(outStreamCapture)); - Logger testLogger = Mockito.mock(Logger.class); + Logger testLogger = mock(Logger.class); assertDoesNotThrow(() -> new WorkflowRuntimeBuilder(testLogger).registerWorkflow(TestWorkflow.class)); assertDoesNotThrow(() -> new WorkflowRuntimeBuilder(testLogger).registerActivity(TestActivity.class)); - WorkflowRuntimeBuilder wfRuntime = new WorkflowRuntimeBuilder(); + WorkflowRuntimeBuilder workflowRuntimeBuilder = new WorkflowRuntimeBuilder(); - wfRuntime.build(); + try (WorkflowRuntime runtime = workflowRuntimeBuilder.build()) { + verify(testLogger, times(1)) + .info(eq("Registered Workflow: {}"), eq("TestWorkflow")); - Mockito.verify(testLogger, Mockito.times(1)) - .info(Mockito.eq("Registered Workflow: TestWorkflow")); - Mockito.verify(testLogger, Mockito.times(1)) - .info(Mockito.eq("Registered Activity: TestActivity")); + verify(testLogger, times(1)) + .info(eq("Registered Activity: {}"), eq("TestActivity")); + } } } From f73b989333c54cb2b1479ef7912d2c26437ebd0f Mon Sep 17 00:00:00 2001 From: Laurent Broudoux Date: Thu, 6 Feb 2025 20:02:21 +0100 Subject: [PATCH 11/21] feat: Adding basic HTTPEndpoint configuration support in testcontainers module (#1210) * feat: Adding basic HTTPEndpoint configuration support in testcontainers module Signed-off-by: Laurent Broudoux * feat: #1209 Adding test for HTTPEndpoint in testcontainers module Signed-off-by: Laurent Broudoux --------- Signed-off-by: Laurent Broudoux Signed-off-by: Christian Kaps --- .../io/dapr/testcontainers/DaprContainer.java | 21 ++++++++++ .../io/dapr/testcontainers/HttpEndpoint.java | 19 +++++++++ .../converter/HttpEndpointYamlConverter.java | 32 +++++++++++++++ .../HttpEndpointYamlConverterTest.java | 40 +++++++++++++++++++ 4 files changed, 112 insertions(+) create mode 100644 testcontainers-dapr/src/main/java/io/dapr/testcontainers/HttpEndpoint.java create mode 100644 testcontainers-dapr/src/main/java/io/dapr/testcontainers/converter/HttpEndpointYamlConverter.java create mode 100644 testcontainers-dapr/src/test/java/io/dapr/testcontainers/converter/HttpEndpointYamlConverterTest.java diff --git a/testcontainers-dapr/src/main/java/io/dapr/testcontainers/DaprContainer.java b/testcontainers-dapr/src/main/java/io/dapr/testcontainers/DaprContainer.java index 9fce309346..9a9ef49870 100644 --- a/testcontainers-dapr/src/main/java/io/dapr/testcontainers/DaprContainer.java +++ b/testcontainers-dapr/src/main/java/io/dapr/testcontainers/DaprContainer.java @@ -15,6 +15,7 @@ import io.dapr.testcontainers.converter.ComponentYamlConverter; import io.dapr.testcontainers.converter.ConfigurationYamlConverter; +import io.dapr.testcontainers.converter.HttpEndpointYamlConverter; import io.dapr.testcontainers.converter.SubscriptionYamlConverter; import io.dapr.testcontainers.converter.YamlConverter; import io.dapr.testcontainers.converter.YamlMapperFactory; @@ -48,6 +49,7 @@ public class DaprContainer extends GenericContainer { private static final Yaml YAML_MAPPER = YamlMapperFactory.create(); private static final YamlConverter COMPONENT_CONVERTER = new ComponentYamlConverter(YAML_MAPPER); private static final YamlConverter SUBSCRIPTION_CONVERTER = new SubscriptionYamlConverter(YAML_MAPPER); + private static final YamlConverter HTTPENDPOINT_CONVERTER = new HttpEndpointYamlConverter(YAML_MAPPER); private static final YamlConverter CONFIGURATION_CONVERTER = new ConfigurationYamlConverter( YAML_MAPPER); private static final WaitStrategy WAIT_STRATEGY = Wait.forHttp("/v1.0/healthz/outbound") @@ -56,6 +58,7 @@ public class DaprContainer extends GenericContainer { private final Set components = new HashSet<>(); private final Set subscriptions = new HashSet<>(); + private final Set httpEndpoints = new HashSet<>(); private DaprLogLevel daprLogLevel = DaprLogLevel.INFO; private String appChannelAddress = "localhost"; private String placementService = "placement"; @@ -99,6 +102,10 @@ public Set getSubscriptions() { return subscriptions; } + public Set getHttpEndpoints() { + return httpEndpoints; + } + public DaprContainer withAppPort(Integer port) { this.appPort = port; return this; @@ -134,6 +141,11 @@ public DaprContainer withSubscription(Subscription subscription) { return this; } + public DaprContainer withHttpEndpoint(HttpEndpoint httpEndpoint) { + httpEndpoints.add(httpEndpoint); + return this; + } + public DaprContainer withPlacementImage(String placementDockerImageName) { this.placementDockerImageName = placementDockerImageName; return this; @@ -291,6 +303,15 @@ protected void configure() { withCopyToContainer(Transferable.of(subscriptionYaml), "/dapr-resources/" + subscription.getName() + ".yaml"); } + for (HttpEndpoint endpoint : httpEndpoints) { + String endpointYaml = HTTPENDPOINT_CONVERTER.convert(endpoint); + + LOGGER.info("> HTTPEndpoint YAML: \n"); + LOGGER.info("\t\n" + endpointYaml + "\n"); + + withCopyToContainer(Transferable.of(endpointYaml), "/dapr-resources/" + endpoint.getName() + ".yaml"); + } + dependsOn(placementContainer); } diff --git a/testcontainers-dapr/src/main/java/io/dapr/testcontainers/HttpEndpoint.java b/testcontainers-dapr/src/main/java/io/dapr/testcontainers/HttpEndpoint.java new file mode 100644 index 0000000000..482cac9a76 --- /dev/null +++ b/testcontainers-dapr/src/main/java/io/dapr/testcontainers/HttpEndpoint.java @@ -0,0 +1,19 @@ +package io.dapr.testcontainers; + +public class HttpEndpoint { + private String name; + private String baseUrl; + + public HttpEndpoint(String name, String baseUrl) { + this.name = name; + this.baseUrl = baseUrl; + } + + public String getName() { + return name; + } + + public String getBaseUrl() { + return baseUrl; + } +} diff --git a/testcontainers-dapr/src/main/java/io/dapr/testcontainers/converter/HttpEndpointYamlConverter.java b/testcontainers-dapr/src/main/java/io/dapr/testcontainers/converter/HttpEndpointYamlConverter.java new file mode 100644 index 0000000000..db4a9cba43 --- /dev/null +++ b/testcontainers-dapr/src/main/java/io/dapr/testcontainers/converter/HttpEndpointYamlConverter.java @@ -0,0 +1,32 @@ +package io.dapr.testcontainers.converter; + +import io.dapr.testcontainers.HttpEndpoint; +import org.yaml.snakeyaml.Yaml; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class HttpEndpointYamlConverter implements YamlConverter { + private final Yaml mapper; + + public HttpEndpointYamlConverter(Yaml mapper) { + this.mapper = mapper; + } + + @Override + public String convert(HttpEndpoint endpoint) { + Map endpointProps = new LinkedHashMap<>(); + endpointProps.put("apiVersion", "dapr.io/v1alpha1"); + endpointProps.put("kind", "HTTPEndpoint"); + + Map endpointMetadata = new LinkedHashMap<>(); + endpointMetadata.put("name", endpoint.getName()); + endpointProps.put("metadata", endpointMetadata); + + Map endpointSpec = new LinkedHashMap<>(); + endpointSpec.put("baseUrl", endpoint.getBaseUrl()); + endpointProps.put("spec", endpointSpec); + + return mapper.dumpAsMap(endpointProps); + } +} diff --git a/testcontainers-dapr/src/test/java/io/dapr/testcontainers/converter/HttpEndpointYamlConverterTest.java b/testcontainers-dapr/src/test/java/io/dapr/testcontainers/converter/HttpEndpointYamlConverterTest.java new file mode 100644 index 0000000000..ec2540fe92 --- /dev/null +++ b/testcontainers-dapr/src/test/java/io/dapr/testcontainers/converter/HttpEndpointYamlConverterTest.java @@ -0,0 +1,40 @@ +package io.dapr.testcontainers.converter; + +import io.dapr.testcontainers.DaprContainer; +import io.dapr.testcontainers.HttpEndpoint; +import org.junit.jupiter.api.Test; +import org.yaml.snakeyaml.Yaml; + +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class HttpEndpointYamlConverterTest { + private final Yaml MAPPER = YamlMapperFactory.create(); + + private final HttpEndpointYamlConverter converter = new HttpEndpointYamlConverter(MAPPER); + + @Test + void testHttpEndpointToYaml() { + DaprContainer dapr = new DaprContainer("daprio/daprd") + .withAppName("dapr-app") + .withAppPort(8081) + .withHttpEndpoint(new HttpEndpoint("my-endpoint", "http://localhost:8080")) + .withAppChannelAddress("host.testcontainers.internal"); + + Set endpoints = dapr.getHttpEndpoints(); + assertEquals(1, endpoints.size()); + + HttpEndpoint endpoint = endpoints.iterator().next(); + String endpointYaml = converter.convert(endpoint); + String expectedEndpointYaml = + "apiVersion: dapr.io/v1alpha1\n" + + "kind: HTTPEndpoint\n" + + "metadata:\n" + + " name: my-endpoint\n" + + "spec:\n" + + " baseUrl: http://localhost:8080\n"; + + assertEquals(expectedEndpointYaml, endpointYaml); + } +} From 8cbf70192484cb2f14dfeaf7e7df3ffee0b1c22c Mon Sep 17 00:00:00 2001 From: salaboy Date: Fri, 7 Feb 2025 09:16:55 +0000 Subject: [PATCH 12/21] fixing actors IT test and messaging IT with app-health-checks Signed-off-by: Christian Kaps --- .../it/spring/messaging/DaprSpringMessagingIT.java | 14 ++++++-------- .../it/spring/messaging/TestRestController.java | 8 ++++++-- .../io/dapr/it/testcontainers/DaprActorsIT.java | 8 ++++++-- .../it/testcontainers/TestActorsApplication.java | 9 +++++++++ .../java/io/dapr/testcontainers/DaprContainer.java | 4 ++++ 5 files changed, 31 insertions(+), 12 deletions(-) diff --git a/sdk-tests/src/test/java/io/dapr/it/spring/messaging/DaprSpringMessagingIT.java b/sdk-tests/src/test/java/io/dapr/it/spring/messaging/DaprSpringMessagingIT.java index fc03f4412a..050dcf6e16 100644 --- a/sdk-tests/src/test/java/io/dapr/it/spring/messaging/DaprSpringMessagingIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/spring/messaging/DaprSpringMessagingIT.java @@ -19,6 +19,7 @@ import io.dapr.testcontainers.Component; import io.dapr.testcontainers.DaprContainer; import io.dapr.testcontainers.DaprLogLevel; +import io.dapr.testcontainers.Subscription; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Tag; @@ -45,7 +46,7 @@ webEnvironment = WebEnvironment.DEFINED_PORT, classes = { DaprClientAutoConfiguration.class, - TestApplication.class + TestApplication.class, TestRestController.class }, properties = {"dapr.pubsub.name=pubsub"} ) @@ -61,10 +62,11 @@ public class DaprSpringMessagingIT { @Container @ServiceConnection - private static final DaprContainer DAPR_CONTAINER = new DaprContainer("daprio/daprd:1.13.2") + private static final DaprContainer DAPR_CONTAINER = new DaprContainer("daprio/daprd:1.14.4") .withAppName("messaging-dapr-app") .withNetwork(DAPR_NETWORK) .withComponent(new Component("pubsub", "pubsub.in-memory", "v1", Collections.emptyMap())) + .withSubscription(new Subscription("my-app-subscription", "pubsub", "mockTopic", "subscribe")) .withAppPort(8080) .withDaprLogLevel(DaprLogLevel.DEBUG) .withLogConsumer(outputFrame -> System.out.println(outputFrame.getUtf8String())) @@ -81,14 +83,10 @@ public static void beforeAll(){ org.testcontainers.Testcontainers.exposeHostPorts(8080); } - @BeforeEach - public void beforeEach() throws InterruptedException { - Thread.sleep(1000); - } @Test - @Disabled("Test is flaky due to global state in the spring test application.") public void testDaprMessagingTemplate() throws InterruptedException { + Thread.sleep(10000); for (int i = 0; i < 10; i++) { var msg = "ProduceAndReadWithPrimitiveMessageType:" + i; @@ -98,7 +96,7 @@ public void testDaprMessagingTemplate() throws InterruptedException { } // Wait for the messages to arrive - Thread.sleep(1000); + Thread.sleep(10000); List> events = testRestController.getEvents(); diff --git a/sdk-tests/src/test/java/io/dapr/it/spring/messaging/TestRestController.java b/sdk-tests/src/test/java/io/dapr/it/spring/messaging/TestRestController.java index a5d12093c0..d1ccb127f6 100644 --- a/sdk-tests/src/test/java/io/dapr/it/spring/messaging/TestRestController.java +++ b/sdk-tests/src/test/java/io/dapr/it/spring/messaging/TestRestController.java @@ -33,13 +33,17 @@ public class TestRestController { private static final Logger LOG = LoggerFactory.getLogger(TestRestController.class); private final List> events = new ArrayList<>(); - @GetMapping("/") + public TestRestController() { + System.out.println("TestRestController started..."); + } + + @GetMapping("/actuator/health") public String ok() { return "OK"; } @Topic(name = topicName, pubsubName = pubSubName) - @PostMapping("/subscribe") + @PostMapping("subscribe") public void handleMessages(@RequestBody CloudEvent event) { LOG.info("++++++CONSUME {}------", event); events.add(event); diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprActorsIT.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprActorsIT.java index a8ed503e73..cb1b24c8ee 100644 --- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprActorsIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprActorsIT.java @@ -25,7 +25,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; @SpringBootTest( - webEnvironment = WebEnvironment.RANDOM_PORT, + webEnvironment = WebEnvironment.DEFINED_PORT, classes = { TestActorsApplication.class, TestDaprActorsConfiguration.class @@ -44,7 +44,8 @@ public class DaprActorsIT { Map.of("actorStateStore", "true"))) .withDaprLogLevel(DaprLogLevel.DEBUG) .withLogConsumer(outputFrame -> System.out.println(outputFrame.getUtf8String())) - .withAppChannelAddress("host.testcontainers.internal"); + .withAppChannelAddress("host.testcontainers.internal") + .withAppPort(8080); /** * Expose the Dapr ports to the host. @@ -65,11 +66,14 @@ static void daprProperties(DynamicPropertyRegistry registry) { @BeforeEach public void setUp(){ + org.testcontainers.Testcontainers.exposeHostPorts(8080); daprActorRuntime.registerActor(TestActorImpl.class); } @Test public void testActors() throws Exception { + Thread.sleep(10000); + ActorProxyBuilder builder = new ActorProxyBuilder<>(TestActor.class, daprActorClient); ActorId actorId = ActorId.createRandom(); TestActor actor = builder.build(actorId); diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestActorsApplication.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestActorsApplication.java index ba9a748c8a..e556462189 100644 --- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestActorsApplication.java +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestActorsApplication.java @@ -15,11 +15,20 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; @SpringBootApplication +@RestController public class TestActorsApplication { public static void main(String[] args) { SpringApplication.run(TestActorsApplication.class, args); } + + //Mocking the actuator health endpoint for the sidecar health check + @GetMapping("/actuator/health") + public String health(){ + return "OK"; + } } diff --git a/testcontainers-dapr/src/main/java/io/dapr/testcontainers/DaprContainer.java b/testcontainers-dapr/src/main/java/io/dapr/testcontainers/DaprContainer.java index 9a9ef49870..7193a622cb 100644 --- a/testcontainers-dapr/src/main/java/io/dapr/testcontainers/DaprContainer.java +++ b/testcontainers-dapr/src/main/java/io/dapr/testcontainers/DaprContainer.java @@ -251,6 +251,10 @@ protected void configure() { cmds.add(Integer.toString(appPort)); } + cmds.add("--enable-app-health-check"); + cmds.add("--app-health-check-path"); + cmds.add("/actuator/health"); + if (configuration != null) { cmds.add("--config"); cmds.add("/dapr-resources/" + configuration.getName() + ".yaml"); From 737de67ab487a1d676ef5eb46b1a7b5bde0233d0 Mon Sep 17 00:00:00 2001 From: artur-ciocanu Date: Mon, 10 Feb 2025 02:02:43 +0200 Subject: [PATCH 13/21] Add app health check support to Dapr Testcontainer (#1213) * Add app health check support to Dapr Testcontainer Signed-off-by: Artur Ciocanu * Some minor cleanup Signed-off-by: Artur Ciocanu * Move waiting to beforeEach, it looks more natural Signed-off-by: Artur Ciocanu --------- Signed-off-by: Artur Ciocanu Co-authored-by: Artur Ciocanu Signed-off-by: Christian Kaps --- .../messaging/DaprSpringMessagingIT.java | 25 +++++++++++-------- .../spring/messaging/TestRestController.java | 8 ++---- .../io/dapr/testcontainers/DaprContainer.java | 22 ++++++++++------ 3 files changed, 32 insertions(+), 23 deletions(-) diff --git a/sdk-tests/src/test/java/io/dapr/it/spring/messaging/DaprSpringMessagingIT.java b/sdk-tests/src/test/java/io/dapr/it/spring/messaging/DaprSpringMessagingIT.java index 050dcf6e16..ca912a4b95 100644 --- a/sdk-tests/src/test/java/io/dapr/it/spring/messaging/DaprSpringMessagingIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/spring/messaging/DaprSpringMessagingIT.java @@ -24,16 +24,14 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.Disabled; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; -import org.springframework.test.context.DynamicPropertyRegistry; -import org.springframework.test.context.DynamicPropertySource; import org.testcontainers.containers.Network; +import org.testcontainers.containers.wait.strategy.Wait; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; @@ -57,8 +55,9 @@ public class DaprSpringMessagingIT { private static final Logger logger = LoggerFactory.getLogger(DaprSpringMessagingIT.class); private static final String TOPIC = "mockTopic"; - private static final Network DAPR_NETWORK = Network.newNetwork(); + private static final int APP_PORT = 8080; + private static final String SUBSCRIPTION_MESSAGE_PATTERN = ".*app is subscribed to the following topics.*"; @Container @ServiceConnection @@ -66,8 +65,9 @@ public class DaprSpringMessagingIT { .withAppName("messaging-dapr-app") .withNetwork(DAPR_NETWORK) .withComponent(new Component("pubsub", "pubsub.in-memory", "v1", Collections.emptyMap())) - .withSubscription(new Subscription("my-app-subscription", "pubsub", "mockTopic", "subscribe")) - .withAppPort(8080) + .withSubscription(new Subscription("my-app-subscription", "pubsub", "mockTopic", "subscribe")) + .withAppPort(APP_PORT) + .withAppHealthCheckPath("/ready") .withDaprLogLevel(DaprLogLevel.DEBUG) .withLogConsumer(outputFrame -> System.out.println(outputFrame.getUtf8String())) .withAppChannelAddress("host.testcontainers.internal"); @@ -80,9 +80,14 @@ public class DaprSpringMessagingIT { @BeforeAll public static void beforeAll(){ - org.testcontainers.Testcontainers.exposeHostPorts(8080); + org.testcontainers.Testcontainers.exposeHostPorts(APP_PORT); + } + + @BeforeEach + public void beforeEach() { + // Ensure the subscriptions are registered + Wait.forLogMessage(SUBSCRIPTION_MESSAGE_PATTERN, 1).waitUntilReady(DAPR_CONTAINER); } - @Test public void testDaprMessagingTemplate() throws InterruptedException { @@ -96,10 +101,10 @@ public void testDaprMessagingTemplate() throws InterruptedException { } // Wait for the messages to arrive - Thread.sleep(10000); + Thread.sleep(1000); List> events = testRestController.getEvents(); assertThat(events.size()).isEqualTo(10); } -} +} \ No newline at end of file diff --git a/sdk-tests/src/test/java/io/dapr/it/spring/messaging/TestRestController.java b/sdk-tests/src/test/java/io/dapr/it/spring/messaging/TestRestController.java index d1ccb127f6..5452ac1bd9 100644 --- a/sdk-tests/src/test/java/io/dapr/it/spring/messaging/TestRestController.java +++ b/sdk-tests/src/test/java/io/dapr/it/spring/messaging/TestRestController.java @@ -33,11 +33,7 @@ public class TestRestController { private static final Logger LOG = LoggerFactory.getLogger(TestRestController.class); private final List> events = new ArrayList<>(); - public TestRestController() { - System.out.println("TestRestController started..."); - } - - @GetMapping("/actuator/health") + @GetMapping("/ready") public String ok() { return "OK"; } @@ -53,4 +49,4 @@ public List> getEvents() { return events; } -} +} \ No newline at end of file diff --git a/testcontainers-dapr/src/main/java/io/dapr/testcontainers/DaprContainer.java b/testcontainers-dapr/src/main/java/io/dapr/testcontainers/DaprContainer.java index 7193a622cb..9f2c448a65 100644 --- a/testcontainers-dapr/src/main/java/io/dapr/testcontainers/DaprContainer.java +++ b/testcontainers-dapr/src/main/java/io/dapr/testcontainers/DaprContainer.java @@ -68,6 +68,7 @@ public class DaprContainer extends GenericContainer { private DaprPlacementContainer placementContainer; private String appName; private Integer appPort; + private String appHealthCheckPath; private boolean shouldReusePlacement; /** @@ -116,6 +117,11 @@ public DaprContainer withAppChannelAddress(String appChannelAddress) { return this; } + public DaprContainer withAppHealthCheckPath(String appHealthCheckPath) { + this.appHealthCheckPath = appHealthCheckPath; + return this; + } + public DaprContainer withConfiguration(Configuration configuration) { this.configuration = configuration; return this; @@ -173,7 +179,7 @@ public DaprContainer withComponent(Component component) { */ public DaprContainer withComponent(Path path) { try { - Map component = this.YAML_MAPPER.loadAs(Files.newInputStream(path), Map.class); + Map component = YAML_MAPPER.loadAs(Files.newInputStream(path), Map.class); String type = (String) component.get("type"); Map metadata = (Map) component.get("metadata"); @@ -233,12 +239,12 @@ protected void configure() { List cmds = new ArrayList<>(); cmds.add("./daprd"); - cmds.add("-app-id"); + cmds.add("--app-id"); cmds.add(appName); cmds.add("--dapr-listen-addresses=0.0.0.0"); cmds.add("--app-protocol"); cmds.add(DAPR_PROTOCOL.getName()); - cmds.add("-placement-host-address"); + cmds.add("--placement-host-address"); cmds.add(placementService + ":50005"); if (appChannelAddress != null && !appChannelAddress.isEmpty()) { @@ -251,9 +257,11 @@ protected void configure() { cmds.add(Integer.toString(appPort)); } - cmds.add("--enable-app-health-check"); - cmds.add("--app-health-check-path"); - cmds.add("/actuator/health"); + if (appHealthCheckPath != null && !appHealthCheckPath.isEmpty()) { + cmds.add("--enable-app-health-check"); + cmds.add("--app-health-check-path"); + cmds.add(appHealthCheckPath); + } if (configuration != null) { cmds.add("--config"); @@ -349,4 +357,4 @@ public boolean equals(Object o) { public int hashCode() { return super.hashCode(); } -} +} \ No newline at end of file From cb290c87f75e0eef0132511f83abebe5803ac562 Mon Sep 17 00:00:00 2001 From: salaboy Date: Thu, 20 Feb 2025 10:09:14 +0000 Subject: [PATCH 14/21] adding license headers + adding wait for actors in test Signed-off-by: Christian Kaps --- .../dapr/it/testcontainers/DaprActorsIT.java | 21 +++++++++++++++++-- .../io/dapr/it/testcontainers/TestActor.java | 13 ++++++++++++ .../dapr/it/testcontainers/TestActorImpl.java | 13 ++++++++++++ .../TestDaprActorsConfiguration.java | 13 ++++++++++++ .../TestDaprWorkflowsConfiguration.java | 13 ++++++++++++ 5 files changed, 71 insertions(+), 2 deletions(-) diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprActorsIT.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprActorsIT.java index cb1b24c8ee..c572b0027c 100644 --- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprActorsIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprActorsIT.java @@ -1,3 +1,16 @@ +/* + * Copyright 2025 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file 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.dapr.it.testcontainers; import io.dapr.actors.ActorId; @@ -16,6 +29,7 @@ import org.springframework.test.context.DynamicPropertyRegistry; import org.springframework.test.context.DynamicPropertySource; import org.testcontainers.containers.Network; +import org.testcontainers.containers.wait.strategy.Wait; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; @@ -36,8 +50,10 @@ public class DaprActorsIT { private static final Network DAPR_NETWORK = Network.newNetwork(); + private static final String ACTORS_MESSAGE_PATTERN = ".*Actor API level in the cluster has been updated to 10.*"; + @Container - private static final DaprContainer DAPR_CONTAINER = new DaprContainer("daprio/daprd:1.14.1") + private static final DaprContainer DAPR_CONTAINER = new DaprContainer("daprio/daprd:1.14.4") .withAppName("actor-dapr-app") .withNetwork(DAPR_NETWORK) .withComponent(new Component("kvstore", "state.in-memory", "v1", @@ -68,11 +84,12 @@ static void daprProperties(DynamicPropertyRegistry registry) { public void setUp(){ org.testcontainers.Testcontainers.exposeHostPorts(8080); daprActorRuntime.registerActor(TestActorImpl.class); + // Ensure the subscriptions are registered + Wait.forLogMessage(ACTORS_MESSAGE_PATTERN, 1).waitUntilReady(DAPR_CONTAINER); } @Test public void testActors() throws Exception { - Thread.sleep(10000); ActorProxyBuilder builder = new ActorProxyBuilder<>(TestActor.class, daprActorClient); ActorId actorId = ActorId.createRandom(); diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestActor.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestActor.java index a3f8ef6d06..c5457fa551 100644 --- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestActor.java +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestActor.java @@ -1,3 +1,16 @@ +/* + * Copyright 2025 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file 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.dapr.it.testcontainers; import io.dapr.actors.ActorMethod; import io.dapr.actors.ActorType; diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestActorImpl.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestActorImpl.java index 0a5febddef..c171642992 100644 --- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestActorImpl.java +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestActorImpl.java @@ -1,3 +1,16 @@ +/* + * Copyright 2025 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file 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.dapr.it.testcontainers; import io.dapr.actors.ActorId; diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestDaprActorsConfiguration.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestDaprActorsConfiguration.java index f004e16047..23f2cf2e0c 100644 --- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestDaprActorsConfiguration.java +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestDaprActorsConfiguration.java @@ -1,3 +1,16 @@ +/* + * Copyright 2025 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file 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.dapr.it.testcontainers; import java.util.Map; diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestDaprWorkflowsConfiguration.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestDaprWorkflowsConfiguration.java index 982d7e89fd..0a2487b70c 100644 --- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestDaprWorkflowsConfiguration.java +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/TestDaprWorkflowsConfiguration.java @@ -1,3 +1,16 @@ +/* + * Copyright 2025 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file 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.dapr.it.testcontainers; import com.fasterxml.jackson.databind.ObjectMapper; From f0287e7889cb2be21f140bccc46d8c7b5f132e70 Mon Sep 17 00:00:00 2001 From: artur-ciocanu Date: Mon, 10 Feb 2025 02:02:43 +0200 Subject: [PATCH 15/21] Add app health check support to Dapr Testcontainer (#1213) * Add app health check support to Dapr Testcontainer Signed-off-by: Artur Ciocanu * Some minor cleanup Signed-off-by: Artur Ciocanu * Move waiting to beforeEach, it looks more natural Signed-off-by: Artur Ciocanu --------- Signed-off-by: Artur Ciocanu Co-authored-by: Artur Ciocanu Signed-off-by: Christian Kaps --- .../dapr/it/spring/messaging/DaprSpringMessagingIT.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/sdk-tests/src/test/java/io/dapr/it/spring/messaging/DaprSpringMessagingIT.java b/sdk-tests/src/test/java/io/dapr/it/spring/messaging/DaprSpringMessagingIT.java index ca912a4b95..154fe2335b 100644 --- a/sdk-tests/src/test/java/io/dapr/it/spring/messaging/DaprSpringMessagingIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/spring/messaging/DaprSpringMessagingIT.java @@ -19,7 +19,6 @@ import io.dapr.testcontainers.Component; import io.dapr.testcontainers.DaprContainer; import io.dapr.testcontainers.DaprLogLevel; -import io.dapr.testcontainers.Subscription; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Tag; @@ -44,7 +43,7 @@ webEnvironment = WebEnvironment.DEFINED_PORT, classes = { DaprClientAutoConfiguration.class, - TestApplication.class, TestRestController.class + TestApplication.class }, properties = {"dapr.pubsub.name=pubsub"} ) @@ -61,11 +60,10 @@ public class DaprSpringMessagingIT { @Container @ServiceConnection - private static final DaprContainer DAPR_CONTAINER = new DaprContainer("daprio/daprd:1.14.4") + private static final DaprContainer DAPR_CONTAINER = new DaprContainer("daprio/daprd:1.13.2") .withAppName("messaging-dapr-app") .withNetwork(DAPR_NETWORK) .withComponent(new Component("pubsub", "pubsub.in-memory", "v1", Collections.emptyMap())) - .withSubscription(new Subscription("my-app-subscription", "pubsub", "mockTopic", "subscribe")) .withAppPort(APP_PORT) .withAppHealthCheckPath("/ready") .withDaprLogLevel(DaprLogLevel.DEBUG) @@ -82,7 +80,7 @@ public class DaprSpringMessagingIT { public static void beforeAll(){ org.testcontainers.Testcontainers.exposeHostPorts(APP_PORT); } - + @BeforeEach public void beforeEach() { // Ensure the subscriptions are registered @@ -91,7 +89,6 @@ public void beforeEach() { @Test public void testDaprMessagingTemplate() throws InterruptedException { - Thread.sleep(10000); for (int i = 0; i < 10; i++) { var msg = "ProduceAndReadWithPrimitiveMessageType:" + i; From d19886579fe6c2e5adf9ea5665cec529572b585a Mon Sep 17 00:00:00 2001 From: Artur Souza Date: Thu, 20 Feb 2025 16:24:30 -0800 Subject: [PATCH 16/21] Picks a port for DaprActorITS for test containers to avoid conflict. Signed-off-by: Artur Souza Signed-off-by: Christian Kaps --- .../java/io/dapr/it/testcontainers/DaprActorsIT.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprActorsIT.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprActorsIT.java index c572b0027c..ad0888fd09 100644 --- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprActorsIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprActorsIT.java @@ -43,11 +43,17 @@ classes = { TestActorsApplication.class, TestDaprActorsConfiguration.class + }, + properties = { + "server.port=64080", // must be constant, not a static attribute from class below. } ) @Testcontainers @Tag("testcontainers") public class DaprActorsIT { + + private static final int APP_PORT = 64080; + private static final Network DAPR_NETWORK = Network.newNetwork(); private static final String ACTORS_MESSAGE_PATTERN = ".*Actor API level in the cluster has been updated to 10.*"; @@ -61,7 +67,7 @@ public class DaprActorsIT { .withDaprLogLevel(DaprLogLevel.DEBUG) .withLogConsumer(outputFrame -> System.out.println(outputFrame.getUtf8String())) .withAppChannelAddress("host.testcontainers.internal") - .withAppPort(8080); + .withAppPort(APP_PORT); /** * Expose the Dapr ports to the host. @@ -82,7 +88,7 @@ static void daprProperties(DynamicPropertyRegistry registry) { @BeforeEach public void setUp(){ - org.testcontainers.Testcontainers.exposeHostPorts(8080); + org.testcontainers.Testcontainers.exposeHostPorts(APP_PORT); daprActorRuntime.registerActor(TestActorImpl.class); // Ensure the subscriptions are registered Wait.forLogMessage(ACTORS_MESSAGE_PATTERN, 1).waitUntilReady(DAPR_CONTAINER); From 2519af510f970d16912acc3e209e6b36b808fe8e Mon Sep 17 00:00:00 2001 From: artur-ciocanu Date: Mon, 10 Feb 2025 02:02:43 +0200 Subject: [PATCH 17/21] Add app health check support to Dapr Testcontainer (#1213) * Add app health check support to Dapr Testcontainer Signed-off-by: Artur Ciocanu * Some minor cleanup Signed-off-by: Artur Ciocanu * Move waiting to beforeEach, it looks more natural Signed-off-by: Artur Ciocanu --------- Signed-off-by: Artur Ciocanu Co-authored-by: Artur Ciocanu Signed-off-by: Christian Kaps --- .../java/io/dapr/it/spring/messaging/DaprSpringMessagingIT.java | 2 +- .../java/io/dapr/it/spring/messaging/TestRestController.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk-tests/src/test/java/io/dapr/it/spring/messaging/DaprSpringMessagingIT.java b/sdk-tests/src/test/java/io/dapr/it/spring/messaging/DaprSpringMessagingIT.java index 154fe2335b..1e41186df4 100644 --- a/sdk-tests/src/test/java/io/dapr/it/spring/messaging/DaprSpringMessagingIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/spring/messaging/DaprSpringMessagingIT.java @@ -104,4 +104,4 @@ public void testDaprMessagingTemplate() throws InterruptedException { assertThat(events.size()).isEqualTo(10); } -} \ No newline at end of file +} diff --git a/sdk-tests/src/test/java/io/dapr/it/spring/messaging/TestRestController.java b/sdk-tests/src/test/java/io/dapr/it/spring/messaging/TestRestController.java index 5452ac1bd9..26c6dd6679 100644 --- a/sdk-tests/src/test/java/io/dapr/it/spring/messaging/TestRestController.java +++ b/sdk-tests/src/test/java/io/dapr/it/spring/messaging/TestRestController.java @@ -39,7 +39,7 @@ public String ok() { } @Topic(name = topicName, pubsubName = pubSubName) - @PostMapping("subscribe") + @PostMapping("/subscribe") public void handleMessages(@RequestBody CloudEvent event) { LOG.info("++++++CONSUME {}------", event); events.add(event); From a9fdda80807540ae44df03cd00e70e11438edf79 Mon Sep 17 00:00:00 2001 From: salaboy Date: Fri, 21 Feb 2025 08:49:54 +0000 Subject: [PATCH 18/21] using random port thanks to @artur-ciocanu Signed-off-by: Christian Kaps --- .../dapr/it/testcontainers/DaprActorsIT.java | 43 +++++++++---------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprActorsIT.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprActorsIT.java index ad0888fd09..46c65a6c93 100644 --- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprActorsIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprActorsIT.java @@ -34,40 +34,37 @@ import org.testcontainers.junit.jupiter.Testcontainers; import java.util.Map; +import java.util.Random; import java.util.UUID; import static org.junit.jupiter.api.Assertions.assertEquals; @SpringBootTest( - webEnvironment = WebEnvironment.DEFINED_PORT, - classes = { - TestActorsApplication.class, - TestDaprActorsConfiguration.class - }, - properties = { - "server.port=64080", // must be constant, not a static attribute from class below. - } + webEnvironment = WebEnvironment.RANDOM_PORT, + classes = { + TestActorsApplication.class, + TestDaprActorsConfiguration.class + } ) @Testcontainers @Tag("testcontainers") public class DaprActorsIT { - - private static final int APP_PORT = 64080; - private static final Network DAPR_NETWORK = Network.newNetwork(); + private static final Random RANDOM = new Random(); + private static final int PORT = RANDOM.nextInt(1000) + 8000; private static final String ACTORS_MESSAGE_PATTERN = ".*Actor API level in the cluster has been updated to 10.*"; @Container private static final DaprContainer DAPR_CONTAINER = new DaprContainer("daprio/daprd:1.14.4") - .withAppName("actor-dapr-app") - .withNetwork(DAPR_NETWORK) - .withComponent(new Component("kvstore", "state.in-memory", "v1", - Map.of("actorStateStore", "true"))) - .withDaprLogLevel(DaprLogLevel.DEBUG) - .withLogConsumer(outputFrame -> System.out.println(outputFrame.getUtf8String())) - .withAppChannelAddress("host.testcontainers.internal") - .withAppPort(APP_PORT); + .withAppName("actor-dapr-app") + .withNetwork(DAPR_NETWORK) + .withComponent(new Component("kvstore", "state.in-memory", "v1", + Map.of("actorStateStore", "true"))) + .withDaprLogLevel(DaprLogLevel.DEBUG) + .withLogConsumer(outputFrame -> System.out.println(outputFrame.getUtf8String())) + .withAppChannelAddress("host.testcontainers.internal") + .withAppPort(PORT); /** * Expose the Dapr ports to the host. @@ -78,6 +75,7 @@ public class DaprActorsIT { static void daprProperties(DynamicPropertyRegistry registry) { registry.add("dapr.http.endpoint", DAPR_CONTAINER::getHttpEndpoint); registry.add("dapr.grpc.endpoint", DAPR_CONTAINER::getGrpcEndpoint); + registry.add("server.port", () -> PORT); } @Autowired @@ -88,15 +86,14 @@ static void daprProperties(DynamicPropertyRegistry registry) { @BeforeEach public void setUp(){ - org.testcontainers.Testcontainers.exposeHostPorts(APP_PORT); + org.testcontainers.Testcontainers.exposeHostPorts(PORT); daprActorRuntime.registerActor(TestActorImpl.class); // Ensure the subscriptions are registered Wait.forLogMessage(ACTORS_MESSAGE_PATTERN, 1).waitUntilReady(DAPR_CONTAINER); } @Test - public void testActors() throws Exception { - + public void testActors() { ActorProxyBuilder builder = new ActorProxyBuilder<>(TestActor.class, daprActorClient); ActorId actorId = ActorId.createRandom(); TestActor actor = builder.build(actorId); @@ -107,4 +104,4 @@ public void testActors() throws Exception { assertEquals(echoedMessage, message); } -} +} \ No newline at end of file From 6755f198b5fad63f24ac0952ee96a0319335860f Mon Sep 17 00:00:00 2001 From: artur-ciocanu Date: Sat, 1 Mar 2025 19:20:00 +0200 Subject: [PATCH 19/21] Update TestRestController.java Signed-off-by: artur-ciocanu --- .../java/io/dapr/it/spring/messaging/TestRestController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk-tests/src/test/java/io/dapr/it/spring/messaging/TestRestController.java b/sdk-tests/src/test/java/io/dapr/it/spring/messaging/TestRestController.java index 26c6dd6679..963cf6b3f6 100644 --- a/sdk-tests/src/test/java/io/dapr/it/spring/messaging/TestRestController.java +++ b/sdk-tests/src/test/java/io/dapr/it/spring/messaging/TestRestController.java @@ -49,4 +49,4 @@ public List> getEvents() { return events; } -} \ No newline at end of file +} From da40fc5b406490a6c51682b7f3f23374eae9e04a Mon Sep 17 00:00:00 2001 From: artur-ciocanu Date: Sat, 1 Mar 2025 19:20:39 +0200 Subject: [PATCH 20/21] Update DaprActorsIT.java Signed-off-by: artur-ciocanu --- .../src/test/java/io/dapr/it/testcontainers/DaprActorsIT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprActorsIT.java b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprActorsIT.java index 46c65a6c93..69eefd9624 100644 --- a/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprActorsIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprActorsIT.java @@ -104,4 +104,4 @@ public void testActors() { assertEquals(echoedMessage, message); } -} \ No newline at end of file +} From 0e3101e8f9dfb3f78a7935a5cb04d684ede3c26d Mon Sep 17 00:00:00 2001 From: artur-ciocanu Date: Sat, 1 Mar 2025 19:21:20 +0200 Subject: [PATCH 21/21] Update DaprContainer.java Signed-off-by: artur-ciocanu --- .../src/main/java/io/dapr/testcontainers/DaprContainer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testcontainers-dapr/src/main/java/io/dapr/testcontainers/DaprContainer.java b/testcontainers-dapr/src/main/java/io/dapr/testcontainers/DaprContainer.java index 9f2c448a65..145f61deaa 100644 --- a/testcontainers-dapr/src/main/java/io/dapr/testcontainers/DaprContainer.java +++ b/testcontainers-dapr/src/main/java/io/dapr/testcontainers/DaprContainer.java @@ -357,4 +357,4 @@ public boolean equals(Object o) { public int hashCode() { return super.hashCode(); } -} \ No newline at end of file +}