From ccd29fc9fb9f12651bc390baa4a025e356571cfc Mon Sep 17 00:00:00 2001 From: Bill DeRusha Date: Wed, 10 May 2023 11:21:42 -0400 Subject: [PATCH 01/18] Init for workflows Signed-off-by: Bill DeRusha --- examples/components/workflows/redis.yaml | 14 ++ examples/pom.xml | 10 ++ .../dapr/examples/workflows/DemoWorkflow.java | 39 +++++ .../workflows/DemoWorkflowClient.java | 56 +++++++ .../workflows/DemoWorkflowService.java | 46 ++++++ .../java/io/dapr/examples/workflows/README.md | 76 +++++++++ pom.xml | 25 ++- sdk-workflows/pom.xml | 152 ++++++++++++++++++ .../workflows/client/DaprWorkflowClient.java | 121 ++++++++++++++ .../runtime/DaprWorkflowContextImpl.java | 55 +++++++ .../runtime/OrchestratorWrapper.java | 51 ++++++ .../io/dapr/workflows/runtime/Workflow.java | 32 ++++ .../workflows/runtime/WorkflowContext.java | 33 ++++ .../workflows/runtime/WorkflowRuntime.java | 86 ++++++++++ .../client/DaprWorkflowClientTest.java | 60 +++++++ .../runtime/DaprWorkflowContextImplTest.java | 52 ++++++ .../runtime/OrchestratorWrapperTest.java | 54 +++++++ .../runtime/WorkflowRuntimeTest.java | 53 ++++++ 18 files changed, 1013 insertions(+), 2 deletions(-) create mode 100644 examples/components/workflows/redis.yaml create mode 100644 examples/src/main/java/io/dapr/examples/workflows/DemoWorkflow.java create mode 100644 examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowClient.java create mode 100644 examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowService.java create mode 100644 examples/src/main/java/io/dapr/examples/workflows/README.md create mode 100644 sdk-workflows/pom.xml create mode 100644 sdk-workflows/src/main/java/io/dapr/workflows/client/DaprWorkflowClient.java create mode 100644 sdk-workflows/src/main/java/io/dapr/workflows/runtime/DaprWorkflowContextImpl.java create mode 100644 sdk-workflows/src/main/java/io/dapr/workflows/runtime/OrchestratorWrapper.java create mode 100644 sdk-workflows/src/main/java/io/dapr/workflows/runtime/Workflow.java create mode 100644 sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowContext.java create mode 100644 sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowRuntime.java create mode 100644 sdk-workflows/src/test/java/io/dapr/workflows/client/DaprWorkflowClientTest.java create mode 100644 sdk-workflows/src/test/java/io/dapr/workflows/runtime/DaprWorkflowContextImplTest.java create mode 100644 sdk-workflows/src/test/java/io/dapr/workflows/runtime/OrchestratorWrapperTest.java create mode 100644 sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowRuntimeTest.java diff --git a/examples/components/workflows/redis.yaml b/examples/components/workflows/redis.yaml new file mode 100644 index 0000000000..2f676bff80 --- /dev/null +++ b/examples/components/workflows/redis.yaml @@ -0,0 +1,14 @@ +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: statestore +spec: + type: state.redis + version: v1 + metadata: + - name: redisHost + value: localhost:6379 + - name: redisPassword + value: "" + - name: actorStateStore + value: "true" diff --git a/examples/pom.xml b/examples/pom.xml index 0484a56bac..b831afbedc 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -118,6 +118,16 @@ dapr-sdk-actors ${project.version} + + io.dapr + dapr-sdk-workflows + ${project.version} + + + com.microsoft + durabletask-client + 1.1.0 + io.dapr dapr-sdk diff --git a/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflow.java b/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflow.java new file mode 100644 index 0000000000..18ed47df33 --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflow.java @@ -0,0 +1,39 @@ +/* + * Copyright 2021 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.examples.workflows; + +import com.microsoft.durabletask.TaskCanceledException; +import io.dapr.workflows.runtime.Workflow; +import io.dapr.workflows.runtime.WorkflowContext; + +import java.time.Duration; + +/** + * Implementation of the DemoWorkflow for the server side. + */ +public class DemoWorkflow extends Workflow { + + @Override + public void run(WorkflowContext ctx) { + System.out.println("Hi, my name is " + ctx.getName()); + System.out.println("Waiting for event: 'myEvent'..."); + try { + ctx.waitForExternalEvent("myEvent", Duration.ofSeconds(30)).await(); + System.out.println("Received!"); + } catch (TaskCanceledException e) { + System.out.println("Timed out"); + } + ctx.complete("finished"); + } +} diff --git a/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowClient.java b/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowClient.java new file mode 100644 index 0000000000..88ad8f3e36 --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowClient.java @@ -0,0 +1,56 @@ +/* + * Copyright 2021 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.examples.workflows; + +import io.dapr.workflows.client.DaprWorkflowClient; +import java.util.concurrent.TimeoutException; + +/** + * Client for Actor runtime to invoke actor methods. + * 1. Build and install jars: + * mvn clean install + * 2. cd to [repo-root]/examples + * 3. Run the client: + * dapr run --components-path ./components/actors --app-id demoactorclient -- java -jar \ + * target/dapr-java-sdk-examples-exec.jar io.dapr.examples.actors.DemoActorClient + */ +public class DemoWorkflowClient { + + /** + * The main method. + * @param args Input arguments (unused). + * @throws InterruptedException If program has been interrupted. + */ + public static void main(String[] args) throws InterruptedException, TimeoutException { + DaprWorkflowClient client = new DaprWorkflowClient(); + + try (client) { + String workflowName = DemoWorkflow.class.getCanonicalName(); + String instanceId = client.scheduleNewWorkflow(workflowName); + + System.out.printf("Started new workflow instance: %s%n", instanceId); + + // EXAMPLE OF OTHER METHOD CALLS + // TimeUnit.SECOND.sleep(5); + // client.raiseEvent("myEvent", instanceId); + // + // String instanceToTerminateId = client.scheduleNewWorkflow(workflowName); + // client.terminate(instanceToTerminateId); + + } + + System.out.println("Exiting DemoWorkflowClient."); + System.exit(0); + } +} diff --git a/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowService.java b/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowService.java new file mode 100644 index 0000000000..437399e920 --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowService.java @@ -0,0 +1,46 @@ +/* + * Copyright 2021 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.examples.workflows; + +import com.microsoft.durabletask.DurableTaskClient; +import com.microsoft.durabletask.DurableTaskGrpcClientBuilder; +import com.microsoft.durabletask.NewOrchestrationInstanceOptions; +import io.dapr.workflows.runtime.WorkflowRuntime; + +import java.util.concurrent.TimeUnit; + +/** + * Service for Workflow runtime. + * 1. Build and install jars: + * mvn clean install + * 2. cd to [repo-root]/examples + * 3. Run the server: + * dapr run --components-path ./components/actors --app-id demoworkflowservice --dapr-grpc-port 4001 + * java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.DemoWorkflowService + */ +public class DemoWorkflowService { + + /** + * The main method of this app. + * + * @param args The port the app will listen on. + * @throws Exception An Exception. + */ + public static void main(String[] args) throws Exception { + // Register the Workflow with the runtime. + WorkflowRuntime.getInstance().registerWorkflow(DemoWorkflow.class); + WorkflowRuntime.getInstance().start(); + System.out.println("Start workflow runtime"); + } +} diff --git a/examples/src/main/java/io/dapr/examples/workflows/README.md b/examples/src/main/java/io/dapr/examples/workflows/README.md new file mode 100644 index 0000000000..d81e16a221 --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/workflows/README.md @@ -0,0 +1,76 @@ +# Dapr Workflow Sample + +In this example, we'll use Dapr to test workflow features. + +Visit [the Workflow documentation landing page](https://docs.dapr.io/developing-applications/building-blocks/workflow) for more information. + +This example contains the follow classes: + +* DemoWorkflow: An example of a Dapr Workflow. +* DemoWorkflowClient: This application will start workflows using Dapr. +* DemoWorkflowService: An application that registers a workflow to the Dapr workflow runtime engine. It also executes the workflow instance. + +## Pre-requisites + +* [Dapr and Dapr Cli](https://docs.dapr.io/getting-started/install-dapr/). + * Run `dapr init`. +* Java JDK 11 (or greater): + * [Microsoft JDK 11](https://docs.microsoft.com/en-us/java/openjdk/download#openjdk-11) + * [Oracle JDK 11](https://www.oracle.com/technetwork/java/javase/downloads/index.html#JDK11) + * [OpenJDK 11](https://jdk.java.net/11/) +* [Apache Maven](https://maven.apache.org/install.html) version 3.x. + +### Checking out the code + +Clone this repository: + +```sh +git clone https://github.com/dapr/java-sdk.git +cd java-sdk +``` + +Then build the Maven project: + +```sh +# make sure you are in the `java-sdk` directory. +mvn install +``` + +Get into the `examples` directory. +```sh +cd examples +``` + +### Running the demo Workflow service + +The first Java class to consider is `DemoWorkflowService`. Its job is to register an implementation of `DemoWorkflow` in the Dapr's workflow runtime engine. In `DemoWorkflowService.java` file, you will find the `DemoWorkflowService` class and the `main` method. See the code snippet below: + +```java +public class DemoWorkflowService { + + public static void main(String[] args) throws Exception { + // Register the Workflow with the runtime. + WorkflowRuntime.getInstance().registerWorkflow(DemoWorkflow.class); + WorkflowRuntime.getInstance().start(); + } +} +``` + +This application uses `WorkflowRuntime.getInstance().registerWorkflow()` in order to register `DemoWorkflow` as a Workflow in the Dapr Workflow runtime. + +`WorkflowRuntime.getInstance().start()` method will build and start the engine within the Dapr workflow runtime. + +Now, execute the following script in order to run DemoWorkflowService: +```sh +dapr run --app-id demoworkflowservice --resources-path ./components/workflows --dapr-grpc-port 4001 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.DemoWorkflowService +``` + +### Running the Workflow client + +The `DemoWorkflowClient` starts instances of workflows that have been registered with Dapr. + +With the DemoWorkflowService running, use the follow command to start the workflow with the DemoWorkflowClient: + +```sh +dapr run --app-id demoworkflowclient --resources-path ./components/workflows --dapr-grpc-port 4001 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.DemoWorkflowClient +``` diff --git a/pom.xml b/pom.xml index b37fb56581..ef452ea9ca 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,27 @@ io.dapr dapr-sdk-parent pom - 1.9.0-SNAPSHOT + + + org.junit.jupiter + junit-jupiter + + + junit + junit + + + junit + junit + + + com.microsoft + durabletask-client + 1.0.0 + compile + + + 1.9.0-SNAPSHOT dapr-sdk-parent SDK for Dapr. https://dapr.io @@ -88,7 +108,7 @@ org.jacoco jacoco-maven-plugin - 0.8.6 + 0.8.8 @@ -301,6 +321,7 @@ sdk-autogen sdk sdk-actors + sdk-workflows sdk-springboot examples diff --git a/sdk-workflows/pom.xml b/sdk-workflows/pom.xml new file mode 100644 index 0000000000..45ae382d51 --- /dev/null +++ b/sdk-workflows/pom.xml @@ -0,0 +1,152 @@ + + 4.0.0 + + + io.dapr + dapr-sdk-parent + 1.9.0-SNAPSHOT + + + dapr-sdk-workflows + jar + 1.9.0-SNAPSHOT + dapr-sdk-workflows + SDK for Workflows on Dapr + + + false + 1.42.1 + + + + + io.dapr + dapr-sdk + ${project.version} + + + junit + junit + test + + + org.mockito + mockito-core + test + + + com.github.gmazzo + okhttp-mock + 1.4.1 + test + + + org.junit.jupiter + junit-jupiter-api + 5.5.2 + test + + + commons-cli + commons-cli + 1.4 + test + + + org.springframework.boot + spring-boot-starter-web + 2.7.8 + test + + + io.grpc + grpc-testing + ${grpc.version} + test + + + com.microsoft + durabletask-client + 1.1.0 + + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.2.1 + + + attach-sources + + jar-no-fork + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.2.0 + + + attach-javadocs + + jar + + + + + + org.jacoco + jacoco-maven-plugin + 0.8.8 + + + default-prepare-agent + + prepare-agent + + + + report + test + + report + + + target/jacoco-report/ + + + + check + + check + + + + + BUNDLE + + + LINE + COVEREDRATIO + 80% + + + + + + + + + + + + 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 new file mode 100644 index 0000000000..b5515bff2c --- /dev/null +++ b/sdk-workflows/src/main/java/io/dapr/workflows/client/DaprWorkflowClient.java @@ -0,0 +1,121 @@ +/* + * 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.client; + +import com.microsoft.durabletask.DurableTaskClient; +import com.microsoft.durabletask.DurableTaskGrpcClientBuilder; +import io.dapr.config.Properties; +import io.dapr.utils.Version; +import io.grpc.ManagedChannel; +import io.grpc.ManagedChannelBuilder; + +import javax.annotation.Nullable; + +public class DaprWorkflowClient implements AutoCloseable { + + private DurableTaskClient innerClient; + private ManagedChannel grpcChannel; + + /** + * Public constructor for DaprWorkflowClient. This layer constructs the GRPC Channel. + */ + public DaprWorkflowClient() { + this(createGrpcChannel()); + } + + /** + * Private Constructor that passes a created DurableTaskClient and the new GRPC channel. + * + * @param grpcChannel ManagedChannel for GRPC channel. + */ + private DaprWorkflowClient(ManagedChannel grpcChannel) { + this(createDurableTaskClient(grpcChannel), grpcChannel); + } + + /** + * Private Constructor for DaprWorkflowClient. + * + * @param innerClient DurableTaskGrpcClient with GRPC Channel set up. + * @param grpcChannel ManagedChannel for instance variable setting. + * + */ + private DaprWorkflowClient(DurableTaskClient innerClient, ManagedChannel grpcChannel) { + this.innerClient = innerClient; + this.grpcChannel = grpcChannel; + } + + /** + * Static method to create the DurableTaskClient. + * + * @param grpcChannel ManagedChannel for GRPC. + * @return a new instance of a DurableTaskClient with a GRPC channel. + */ + private static DurableTaskClient createDurableTaskClient(ManagedChannel grpcChannel) { + return new DurableTaskGrpcClientBuilder() + .grpcChannel(grpcChannel) + .build(); + } + + /** + * Static method to create the GRPC Channel for the DurableTaskClient. + * + * @return a Managed GRPC channel. + * @throws IllegalStateException if the GRPC port is invalid. + */ + private static ManagedChannel createGrpcChannel() throws IllegalStateException { + int port = Properties.GRPC_PORT.get(); + if (port <= 0) { + throw new IllegalStateException("Invalid port."); + } + + ManagedChannel channel = ManagedChannelBuilder.forAddress(Properties.SIDECAR_IP.get(), port) + .usePlaintext() + .userAgent(Version.getSdkVersion()) + .build(); + return channel; + } + + /** + * Schedules a new workflow using DurableTask client. + * + * @param workflowName name of workflow to start. + * @return String for new Workflow instance id. + */ + public String scheduleNewWorkflow(String workflowName) { + return this.innerClient.scheduleNewOrchestrationInstance(workflowName); + } + + /** + * Terminates the workflow associated with the provided instance id. + * + * @param workflowInstanceId Workflow instance id to terminate. + * @param output the optional output to set for the terminated orchestration instance. + */ + public void terminateWorkflow(String workflowInstanceId, @Nullable Object output) { + this.innerClient.terminate(workflowInstanceId, output); + } + + /** + * Closes the inner DurableTask client and shutdown the GRPC channel. + * + */ + public void close() { + if (this.innerClient != null) { + this.innerClient.close(); + } + if (this.grpcChannel != null && !this.grpcChannel.isShutdown()) { + this.grpcChannel.shutdown(); + } + } +} diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DaprWorkflowContextImpl.java b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DaprWorkflowContextImpl.java new file mode 100644 index 0000000000..f6b7df09e3 --- /dev/null +++ b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DaprWorkflowContextImpl.java @@ -0,0 +1,55 @@ +/* + * 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.Task; +import com.microsoft.durabletask.TaskOrchestrationContext; + +import java.time.Duration; + +public class DaprWorkflowContextImpl implements WorkflowContext { + + private final TaskOrchestrationContext innerContext; + + /** + * Constructor for DaprWorkflowContextImpl. + * + * @param context TaskOrchestrationContext + * @throws IllegalArgumentException if context is null + */ + public DaprWorkflowContextImpl(TaskOrchestrationContext context) throws IllegalArgumentException { + if (context == null) { + throw new IllegalArgumentException("Inner context cannot be null"); + } else { + this.innerContext = context; + } + } + + public String getName() { + return this.innerContext.getName(); + } + + public String getInstanceId() { + return this.innerContext.getInstanceId(); + } + + public void complete(Object o) { + this.innerContext.complete(o); + } + + @Override + public Task waitForExternalEvent(String eventName, Duration timeout) { + return innerContext.waitForExternalEvent(eventName, timeout); + } +} diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/OrchestratorWrapper.java b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/OrchestratorWrapper.java new file mode 100644 index 0000000000..44f6885934 --- /dev/null +++ b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/OrchestratorWrapper.java @@ -0,0 +1,51 @@ +/* + * 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 java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +/** + * Wrapper for Durable Task Framework orchestration factory. + */ +class OrchestratorWrapper implements TaskOrchestrationFactory { + private final Constructor workflowConstructor; + private final String name; + + public OrchestratorWrapper(Class clazz) throws NoSuchMethodException { + this.name = clazz.getCanonicalName(); + this.workflowConstructor = clazz.getDeclaredConstructor(); + } + + @Override + public String getName() { + return name; + } + + @Override + public TaskOrchestration create() { + return ctx -> { + try { + T workflow = this.workflowConstructor.newInstance(); + workflow.run(new DaprWorkflowContextImpl(ctx)); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + }; + + } +} diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/Workflow.java b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/Workflow.java new file mode 100644 index 0000000000..6460ac4a55 --- /dev/null +++ b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/Workflow.java @@ -0,0 +1,32 @@ +/* + * 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 io.dapr.workflows.runtime.WorkflowContext; + +/** + * Common interface for workflow implementations. + */ +public abstract class Workflow { + public Workflow(){ + } + + /** + * Executes the workflow logic. + * + * @param ctx provides access to methods for scheduling durable tasks and getting information about the current + * workflow instance. + */ + public abstract void run(WorkflowContext ctx); +} diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowContext.java b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowContext.java new file mode 100644 index 0000000000..357add98f6 --- /dev/null +++ b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowContext.java @@ -0,0 +1,33 @@ +/* + * 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.Task; +import java.time.Duration; + +/** + * Context object used by workflow implementations to perform actions such as scheduling activities, + * durable timers, waiting for external events, and for getting basic information about the current + * workflow instance. + */ +public interface WorkflowContext { + + String getName(); + + String getInstanceId(); + + void complete(Object o); + + Task waitForExternalEvent(String eventName, Duration timeout); +} diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowRuntime.java b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowRuntime.java new file mode 100644 index 0000000000..22c1145c8f --- /dev/null +++ b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowRuntime.java @@ -0,0 +1,86 @@ +/* + * 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.DurableTaskGrpcWorker; +import com.microsoft.durabletask.DurableTaskGrpcWorkerBuilder; + +/** + * Contains methods to register workflows and activities. + */ +public class WorkflowRuntime implements AutoCloseable { + + private static volatile WorkflowRuntime instance; + private DurableTaskGrpcWorkerBuilder builder; + private DurableTaskGrpcWorker worker; + + private WorkflowRuntime() throws IllegalStateException { + + if (instance != null) { + throw new IllegalStateException("WorkflowRuntime should only be constructed once"); + } + + this.builder = new DurableTaskGrpcWorkerBuilder(); + } + + /** + * Returns an WorkflowRuntime object. + * + * @return An WorkflowRuntime object. + */ + public static WorkflowRuntime getInstance() { + if (instance == null) { + synchronized (WorkflowRuntime.class) { + if (instance == null) { + instance = new WorkflowRuntime(); + } + } + } + return instance; + } + + /** + * Registers a Workflow object. + * + * @param any Workflow type + * @param clazz the class being registered + */ + public void registerWorkflow(Class clazz) { + try { + this.builder = this.builder.addOrchestration( + new OrchestratorWrapper<>(clazz) + ); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + + /** + * Start the Workflow runtime. + */ + public void start() { + this.worker = this.builder.build(); + this.worker.start(); + } + + /** + * {@inheritDoc} + */ + @Override + public void close() { + if (this.worker != null) { + this.worker.close(); + } + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000..099e66c4b0 --- /dev/null +++ b/sdk-workflows/src/test/java/io/dapr/workflows/client/DaprWorkflowClientTest.java @@ -0,0 +1,60 @@ +package io.dapr.workflows.client; + +import com.microsoft.durabletask.DurableTaskClient; +import io.grpc.ManagedChannel; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.lang.reflect.Constructor; +import java.util.Arrays; + +import static org.mockito.Mockito.*; + +public class DaprWorkflowClientTest { + private DaprWorkflowClient client; + + private static Constructor constructor; + private DurableTaskClient mockInnerClient; + private ManagedChannel mockGrpcChannel; + + @BeforeClass + public static void beforeAll() { + constructor = + Constructor.class.cast(Arrays.stream(DaprWorkflowClient.class.getDeclaredConstructors()) + .filter(c -> c.getParameters().length == 2).map(c -> { + c.setAccessible(true); + return c; + }).findFirst().get()); + } + + @Before + public void setUp() throws Exception { + mockInnerClient = mock(DurableTaskClient.class); + mockGrpcChannel = mock(ManagedChannel.class); + client = constructor.newInstance(mockInnerClient, mockGrpcChannel); + } + + @Test + public void scheduleNewWorkflow() { + String expectedArgument = "TestWorkflow"; + + client.scheduleNewWorkflow(expectedArgument); + verify(mockInnerClient, times(1)).scheduleNewOrchestrationInstance(expectedArgument); + } + + @Test + public void terminateWorkflow() { + String expectedArgument = "TestWorkflow"; + + client.terminateWorkflow(expectedArgument, null); + verify(mockInnerClient, times(1)).terminate(expectedArgument, null); + } + + @Test + public void close() { + client.close(); + verify(mockInnerClient, times(1)).close(); + verify(mockGrpcChannel, times(1)).shutdown(); + } +} diff --git a/sdk-workflows/src/test/java/io/dapr/workflows/runtime/DaprWorkflowContextImplTest.java b/sdk-workflows/src/test/java/io/dapr/workflows/runtime/DaprWorkflowContextImplTest.java new file mode 100644 index 0000000000..080c7d3d1e --- /dev/null +++ b/sdk-workflows/src/test/java/io/dapr/workflows/runtime/DaprWorkflowContextImplTest.java @@ -0,0 +1,52 @@ +package io.dapr.workflows.runtime; + +import com.microsoft.durabletask.TaskOrchestrationContext; +import org.junit.Test; +import org.junit.Before; + +import java.time.Duration; + +import static org.mockito.Mockito.*; + +public class DaprWorkflowContextImplTest { + private DaprWorkflowContextImpl context; + private TaskOrchestrationContext mockInnerContext; + + @Before + public void setUp() { + mockInnerContext = mock(TaskOrchestrationContext.class); + context = new DaprWorkflowContextImpl(mockInnerContext); + } + + @Test + public void getNameTest() { + context.getName(); + verify(mockInnerContext, times(1)).getName(); + } + + @Test + public void getInstanceIdTest() { + context.getInstanceId(); + verify(mockInnerContext, times(1)).getInstanceId(); + } + + @Test + public void waitForExternalEventTest() { + String expectedEvent = "TestEvent"; + Duration expectedDuration = Duration.ofSeconds(1); + + context.waitForExternalEvent(expectedEvent, expectedDuration); + verify(mockInnerContext, times(1)).waitForExternalEvent(expectedEvent, expectedDuration); + } + + @Test(expected = IllegalArgumentException.class) + public void DaprWorkflowContextWithEmptyInnerContext() { + context = new DaprWorkflowContextImpl(null); + } + + @Test + public void completeTest() { + context.complete(null); + verify(mockInnerContext, times(1)).complete(null); + } +} diff --git a/sdk-workflows/src/test/java/io/dapr/workflows/runtime/OrchestratorWrapperTest.java b/sdk-workflows/src/test/java/io/dapr/workflows/runtime/OrchestratorWrapperTest.java new file mode 100644 index 0000000000..2ca86f4b64 --- /dev/null +++ b/sdk-workflows/src/test/java/io/dapr/workflows/runtime/OrchestratorWrapperTest.java @@ -0,0 +1,54 @@ +/* + * 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.DurableTaskGrpcWorkerBuilder; +import com.microsoft.durabletask.TaskOrchestrationContext; +import com.microsoft.durabletask.TaskOrchestrationFactory; +import org.junit.Assert; +import org.junit.Test; + +import java.lang.reflect.Constructor; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.mockito.Mockito.*; + +public class OrchestratorWrapperTest { + public static class TestWorkflow extends Workflow{ + @Override + public void run(WorkflowContext ctx) { + ctx.getInstanceId(); + } + } + + @Test + public void getName() throws NoSuchMethodException { + OrchestratorWrapper wrapper = new OrchestratorWrapper(TestWorkflow.class); + Assert.assertEquals( + "io.dapr.workflows.runtime.OrchestratorWrapperTest.TestWorkflow", + wrapper.getName() + ); + } + + @Test + public void createWithClass() throws NoSuchMethodException { + TaskOrchestrationContext mockContext = mock(TaskOrchestrationContext.class); + OrchestratorWrapper wrapper = new OrchestratorWrapper(TestWorkflow.class); + 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/WorkflowRuntimeTest.java b/sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowRuntimeTest.java new file mode 100644 index 0000000000..cd0d4b688a --- /dev/null +++ b/sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowRuntimeTest.java @@ -0,0 +1,53 @@ +/* + * 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.DurableTaskGrpcWorkerBuilder; +import org.junit.Test; + +import java.lang.reflect.Constructor; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +public class WorkflowRuntimeTest { + + private static Constructor constructor; + private DurableTaskGrpcWorkerBuilder mockWorkerBuilder; + + public static class TestWorkflow extends Workflow{ + @Override + public void run(WorkflowContext ctx) { } + } + + @Test + public void registerValidWorkflowClass() { + assertDoesNotThrow(() -> WorkflowRuntime.getInstance().registerWorkflow(TestWorkflow.class)); + } + + @Test + public void startAndClose() { + assertDoesNotThrow(() -> { + WorkflowRuntime.getInstance().start(); + WorkflowRuntime.getInstance().close(); + }); + } + + @Test + public void closeWithoutStarting() { + assertDoesNotThrow(() -> { + WorkflowRuntime.getInstance().close(); + }); + } +} From 22aacbd7d4190bd72371e6b5b4e2a729416db3b0 Mon Sep 17 00:00:00 2001 From: Hannah Kennedy Date: Fri, 26 May 2023 17:18:00 -0400 Subject: [PATCH 02/18] Updating some javadocs and Years. Signed-off-by: Hannah Kennedy --- .../java/io/dapr/examples/workflows/DemoWorkflow.java | 2 +- .../io/dapr/examples/workflows/DemoWorkflowClient.java | 10 ++-------- .../dapr/examples/workflows/DemoWorkflowService.java | 10 ++-------- 3 files changed, 5 insertions(+), 17 deletions(-) diff --git a/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflow.java b/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflow.java index 18ed47df33..ffcb793ecc 100644 --- a/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflow.java +++ b/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflow.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 The Dapr Authors + * 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 diff --git a/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowClient.java b/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowClient.java index 88ad8f3e36..45d33c8be5 100644 --- a/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowClient.java +++ b/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowClient.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 The Dapr Authors + * 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 @@ -17,13 +17,7 @@ import java.util.concurrent.TimeoutException; /** - * Client for Actor runtime to invoke actor methods. - * 1. Build and install jars: - * mvn clean install - * 2. cd to [repo-root]/examples - * 3. Run the client: - * dapr run --components-path ./components/actors --app-id demoactorclient -- java -jar \ - * target/dapr-java-sdk-examples-exec.jar io.dapr.examples.actors.DemoActorClient + * For setup instructions, see the README. */ public class DemoWorkflowClient { diff --git a/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowService.java b/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowService.java index 437399e920..7cc3d3c4df 100644 --- a/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowService.java +++ b/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowService.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 The Dapr Authors + * 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 @@ -21,13 +21,7 @@ import java.util.concurrent.TimeUnit; /** - * Service for Workflow runtime. - * 1. Build and install jars: - * mvn clean install - * 2. cd to [repo-root]/examples - * 3. Run the server: - * dapr run --components-path ./components/actors --app-id demoworkflowservice --dapr-grpc-port 4001 - * java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.DemoWorkflowService + * For setup instructions, see the README. */ public class DemoWorkflowService { From cf3f60fac8bfcc0087bdaacc1170c1a34db0d419 Mon Sep 17 00:00:00 2001 From: Hannah Kennedy Date: Fri, 26 May 2023 17:25:15 -0400 Subject: [PATCH 03/18] Add missing Header Signed-off-by: Hannah Kennedy --- .../workflows/client/DaprWorkflowClientTest.java | 13 +++++++++++++ .../runtime/DaprWorkflowContextImplTest.java | 13 +++++++++++++ 2 files changed, 26 insertions(+) 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 099e66c4b0..742b15c8a0 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 @@ -1,3 +1,16 @@ +/* + * 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.client; import com.microsoft.durabletask.DurableTaskClient; diff --git a/sdk-workflows/src/test/java/io/dapr/workflows/runtime/DaprWorkflowContextImplTest.java b/sdk-workflows/src/test/java/io/dapr/workflows/runtime/DaprWorkflowContextImplTest.java index 080c7d3d1e..0d084a6d98 100644 --- a/sdk-workflows/src/test/java/io/dapr/workflows/runtime/DaprWorkflowContextImplTest.java +++ b/sdk-workflows/src/test/java/io/dapr/workflows/runtime/DaprWorkflowContextImplTest.java @@ -1,3 +1,16 @@ +/* + * 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; From c6e3b5e99ec9cdb9c7f1c4c1953e3fc9c3dbcfea Mon Sep 17 00:00:00 2001 From: Bill DeRusha <444835+bderusha@users.noreply.github.com> Date: Thu, 1 Jun 2023 11:16:51 -0400 Subject: [PATCH 04/18] respond to PR feedback Signed-off-by: Bill DeRusha <444835+bderusha@users.noreply.github.com> --- .../dapr/examples/workflows/DemoWorkflow.java | 13 ++-- .../workflows/DemoWorkflowClient.java | 24 ++++--- ...owService.java => DemoWorkflowWorker.java} | 10 +-- .../java/io/dapr/examples/workflows/README.md | 18 ++--- pom.xml | 2 +- sdk-workflows/pom.xml | 18 ----- .../workflows/client/DaprWorkflowClient.java | 37 ++++++++-- .../runtime/DaprWorkflowContextImpl.java | 45 ++++++++++-- .../workflows/runtime/WorkflowContext.java | 46 +++++++++++- .../workflows/runtime/WorkflowRuntime.java | 21 +++++- .../client/DaprWorkflowClientTest.java | 45 ++++++++++-- .../runtime/DaprWorkflowContextImplTest.java | 70 +++++++++++++++++++ .../runtime/WorkflowRuntimeTest.java | 14 ++-- 13 files changed, 285 insertions(+), 78 deletions(-) rename examples/src/main/java/io/dapr/examples/workflows/{DemoWorkflowService.java => DemoWorkflowWorker.java} (76%) diff --git a/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflow.java b/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflow.java index ffcb793ecc..b5c9c7cc36 100644 --- a/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflow.java +++ b/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflow.java @@ -18,6 +18,7 @@ import io.dapr.workflows.runtime.WorkflowContext; import java.time.Duration; +import java.util.concurrent.TimeoutException; /** * Implementation of the DemoWorkflow for the server side. @@ -26,13 +27,15 @@ public class DemoWorkflow extends Workflow { @Override public void run(WorkflowContext ctx) { - System.out.println("Hi, my name is " + ctx.getName()); - System.out.println("Waiting for event: 'myEvent'..."); + ctx.getOut().println("Starting Workflow: " + ctx.getName()); + ctx.getOut().println("Instance ID: " + ctx.getInstanceId()); + ctx.getOut().println("Waiting for event: 'myEvent'..."); try { - ctx.waitForExternalEvent("myEvent", Duration.ofSeconds(30)).await(); - System.out.println("Received!"); + ctx.waitForExternalEvent("myEvent", Duration.ofSeconds(10)).await(); + ctx.getOut().println("Received!"); } catch (TaskCanceledException e) { - System.out.println("Timed out"); + ctx.getErr().println("Timed out"); + ctx.getErr().println(e.getMessage()); } ctx.complete("finished"); } diff --git a/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowClient.java b/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowClient.java index 45d33c8be5..6d91aa54bf 100644 --- a/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowClient.java +++ b/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowClient.java @@ -14,7 +14,8 @@ package io.dapr.examples.workflows; import io.dapr.workflows.client.DaprWorkflowClient; -import java.util.concurrent.TimeoutException; + +import java.util.concurrent.TimeUnit; /** * For setup instructions, see the README. @@ -26,22 +27,27 @@ public class DemoWorkflowClient { * @param args Input arguments (unused). * @throws InterruptedException If program has been interrupted. */ - public static void main(String[] args) throws InterruptedException, TimeoutException { + public static void main(String[] args) throws InterruptedException { DaprWorkflowClient client = new DaprWorkflowClient(); try (client) { + System.out.println("*****"); String workflowName = DemoWorkflow.class.getCanonicalName(); String instanceId = client.scheduleNewWorkflow(workflowName); + System.out.printf("Started new workflow instance with random ID: %s%n", instanceId); - System.out.printf("Started new workflow instance: %s%n", instanceId); + System.out.println("Sleep and allow this workflow instance to timeout..."); + TimeUnit.SECONDS.sleep(10); - // EXAMPLE OF OTHER METHOD CALLS - // TimeUnit.SECOND.sleep(5); - // client.raiseEvent("myEvent", instanceId); - // - // String instanceToTerminateId = client.scheduleNewWorkflow(workflowName); - // client.terminate(instanceToTerminateId); + System.out.println("*****"); + String instanceToTerminateId = "terminateMe"; + client.scheduleNewWorkflow(workflowName, null, instanceToTerminateId); + System.out.printf("Started new workflow instance with specified ID: %s%n", instanceToTerminateId); + TimeUnit.SECONDS.sleep(5); + System.out.println("Terminate this workflow instance manually before the timeout is reached"); + client.terminateWorkflow(instanceToTerminateId, null); + System.out.println("*****"); } System.out.println("Exiting DemoWorkflowClient."); diff --git a/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowService.java b/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowWorker.java similarity index 76% rename from examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowService.java rename to examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowWorker.java index 7cc3d3c4df..58d363b00a 100644 --- a/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowService.java +++ b/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowWorker.java @@ -13,17 +13,12 @@ package io.dapr.examples.workflows; -import com.microsoft.durabletask.DurableTaskClient; -import com.microsoft.durabletask.DurableTaskGrpcClientBuilder; -import com.microsoft.durabletask.NewOrchestrationInstanceOptions; import io.dapr.workflows.runtime.WorkflowRuntime; -import java.util.concurrent.TimeUnit; - /** * For setup instructions, see the README. */ -public class DemoWorkflowService { +public class DemoWorkflowWorker { /** * The main method of this app. @@ -34,7 +29,8 @@ public class DemoWorkflowService { public static void main(String[] args) throws Exception { // Register the Workflow with the runtime. WorkflowRuntime.getInstance().registerWorkflow(DemoWorkflow.class); - WorkflowRuntime.getInstance().start(); System.out.println("Start workflow runtime"); + WorkflowRuntime.getInstance().startAndBlock(); + System.exit(0); } } diff --git a/examples/src/main/java/io/dapr/examples/workflows/README.md b/examples/src/main/java/io/dapr/examples/workflows/README.md index d81e16a221..81ce80951a 100644 --- a/examples/src/main/java/io/dapr/examples/workflows/README.md +++ b/examples/src/main/java/io/dapr/examples/workflows/README.md @@ -8,7 +8,7 @@ This example contains the follow classes: * DemoWorkflow: An example of a Dapr Workflow. * DemoWorkflowClient: This application will start workflows using Dapr. -* DemoWorkflowService: An application that registers a workflow to the Dapr workflow runtime engine. It also executes the workflow instance. +* DemoWorkflowWorker: An application that registers a workflow to the Dapr workflow runtime engine. It also executes the workflow instance. ## Pre-requisites @@ -41,17 +41,19 @@ Get into the `examples` directory. cd examples ``` -### Running the demo Workflow service +### Running the demo Workflow worker -The first Java class to consider is `DemoWorkflowService`. Its job is to register an implementation of `DemoWorkflow` in the Dapr's workflow runtime engine. In `DemoWorkflowService.java` file, you will find the `DemoWorkflowService` class and the `main` method. See the code snippet below: +The first Java class to consider is `DemoWorkflowWorker`. Its job is to register an implementation of `DemoWorkflow` in the Dapr's workflow runtime engine. In `DemoWorkflowWorker.java` file, you will find the `DemoWorkflowWorker` class and the `main` method. See the code snippet below: ```java -public class DemoWorkflowService { +public class DemoWorkflowWorker { public static void main(String[] args) throws Exception { // Register the Workflow with the runtime. WorkflowRuntime.getInstance().registerWorkflow(DemoWorkflow.class); - WorkflowRuntime.getInstance().start(); + System.out.println("Start workflow runtime"); + WorkflowRuntime.getInstance().startAndBlock(); + System.exit(0); } } ``` @@ -60,16 +62,16 @@ This application uses `WorkflowRuntime.getInstance().registerWorkflow()` in orde `WorkflowRuntime.getInstance().start()` method will build and start the engine within the Dapr workflow runtime. -Now, execute the following script in order to run DemoWorkflowService: +Now, execute the following script in order to run DemoWorkflowWorker: ```sh -dapr run --app-id demoworkflowservice --resources-path ./components/workflows --dapr-grpc-port 4001 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.DemoWorkflowService +dapr run --app-id demoworkflowworker --resources-path ./components/workflows --dapr-grpc-port 4001 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.DemoWorkflowWorker ``` ### Running the Workflow client The `DemoWorkflowClient` starts instances of workflows that have been registered with Dapr. -With the DemoWorkflowService running, use the follow command to start the workflow with the DemoWorkflowClient: +With the DemoWorkflowWorker running, use the follow command to start the workflow with the DemoWorkflowClient: ```sh dapr run --app-id demoworkflowclient --resources-path ./components/workflows --dapr-grpc-port 4001 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.DemoWorkflowClient diff --git a/pom.xml b/pom.xml index ef452ea9ca..3386494c12 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ com.microsoft durabletask-client - 1.0.0 + 1.1.0 compile diff --git a/sdk-workflows/pom.xml b/sdk-workflows/pom.xml index 45ae382d51..46ea6a18a9 100644 --- a/sdk-workflows/pom.xml +++ b/sdk-workflows/pom.xml @@ -37,12 +37,6 @@ mockito-core test - - com.github.gmazzo - okhttp-mock - 1.4.1 - test - org.junit.jupiter junit-jupiter-api @@ -55,18 +49,6 @@ 1.4 test - - org.springframework.boot - spring-boot-starter-web - 2.7.8 - test - - - io.grpc - grpc-testing - ${grpc.version} - test - com.microsoft durabletask-client 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 b5515bff2c..7123ed28ad 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 @@ -21,11 +21,12 @@ import io.grpc.ManagedChannelBuilder; import javax.annotation.Nullable; +import java.util.concurrent.TimeUnit; public class DaprWorkflowClient implements AutoCloseable { - private DurableTaskClient innerClient; - private ManagedChannel grpcChannel; + private final DurableTaskClient innerClient; + private final ManagedChannel grpcChannel; /** * Public constructor for DaprWorkflowClient. This layer constructs the GRPC Channel. @@ -79,23 +80,45 @@ private static ManagedChannel createGrpcChannel() throws IllegalStateException { throw new IllegalStateException("Invalid port."); } - ManagedChannel channel = ManagedChannelBuilder.forAddress(Properties.SIDECAR_IP.get(), port) + return ManagedChannelBuilder.forAddress(Properties.SIDECAR_IP.get(), port) .usePlaintext() .userAgent(Version.getSdkVersion()) .build(); - return channel; } /** * Schedules a new workflow using DurableTask client. * * @param workflowName name of workflow to start. - * @return String for new Workflow instance id. + * @return the randomly-generated instance ID for new Workflow instance. */ public String scheduleNewWorkflow(String workflowName) { return this.innerClient.scheduleNewOrchestrationInstance(workflowName); } + /** + * Schedules a new workflow using DurableTask client. + * + * @param workflowName name of workflow to start. + * @param input the input to pass to the scheduled orchestration instance. Must be serializable. + * @return the randomly-generated instance ID for new Workflow instance. + */ + public String scheduleNewWorkflow(String workflowName, Object input) { + return this.innerClient.scheduleNewOrchestrationInstance(workflowName, input); + } + + /** + * Schedules a new workflow using DurableTask client. + * + * @param workflowName name of workflow to start. + * @param input the input to pass to the scheduled orchestration instance. Must be serializable. + * @param instanceId the unique ID of the orchestration instance to schedule + * @return the instanceId parameter value. + */ + public String scheduleNewWorkflow(String workflowName, Object input, String instanceId) { + return this.innerClient.scheduleNewOrchestrationInstance(workflowName, input, instanceId); + } + /** * Terminates the workflow associated with the provided instance id. * @@ -110,12 +133,12 @@ public void terminateWorkflow(String workflowInstanceId, @Nullable Object output * Closes the inner DurableTask client and shutdown the GRPC channel. * */ - public void close() { + public void close() throws InterruptedException { if (this.innerClient != null) { this.innerClient.close(); } if (this.grpcChannel != null && !this.grpcChannel.isShutdown()) { - this.grpcChannel.shutdown(); + this.grpcChannel.shutdown().awaitTermination(5, TimeUnit.SECONDS); } } } diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DaprWorkflowContextImpl.java b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DaprWorkflowContextImpl.java index f6b7df09e3..29c9408fd6 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DaprWorkflowContextImpl.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DaprWorkflowContextImpl.java @@ -16,11 +16,13 @@ import com.microsoft.durabletask.Task; import com.microsoft.durabletask.TaskOrchestrationContext; +import java.io.OutputStream; +import java.io.PrintStream; import java.time.Duration; public class DaprWorkflowContextImpl implements WorkflowContext { - private final TaskOrchestrationContext innerContext; + private final PrintStream dummyStream; /** * Constructor for DaprWorkflowContextImpl. @@ -34,21 +36,56 @@ public DaprWorkflowContextImpl(TaskOrchestrationContext context) throws IllegalA } else { this.innerContext = context; } + this.dummyStream = new PrintStream(new OutputStream() { + public void write(int b) { + } + }); + } + + /** + * {@inheritDoc} + */ + public PrintStream getOut() { + if (this.innerContext.getIsReplaying()) { + return this.dummyStream; + } + return System.out; } + /** + * {@inheritDoc} + */ + public PrintStream getErr() { + if (this.innerContext.getIsReplaying()) { + return this.dummyStream; + } + return System.err; + } + + /** + * {@inheritDoc} + */ public String getName() { return this.innerContext.getName(); } + /** + * {@inheritDoc} + */ public String getInstanceId() { return this.innerContext.getInstanceId(); } - public void complete(Object o) { - this.innerContext.complete(o); + /** + * {@inheritDoc} + */ + public void complete(Object output) { + this.innerContext.complete(output); } - @Override + /** + * {@inheritDoc} + */ public Task waitForExternalEvent(String eventName, Duration timeout) { return innerContext.waitForExternalEvent(eventName, timeout); } diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowContext.java b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowContext.java index 357add98f6..45cb81df19 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowContext.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowContext.java @@ -14,6 +14,8 @@ package io.dapr.workflows.runtime; import com.microsoft.durabletask.Task; + +import java.io.PrintStream; import java.time.Duration; /** @@ -23,11 +25,53 @@ */ public interface WorkflowContext { + /** + * Get the stdout PrintStream set by {@code System.out} + * only when {@code isReplaying} is false. Otherwise return + * a dummy PrintStream. + * + * @return PrintStream + */ + PrintStream getOut(); + + /** + * Get the stderr PrintStream set by {@code System.err} + * only when {@code isReplaying} is false. Otherwise return + * a dummy PrintStream. + * + * @return PrintStream + */ + public PrintStream getErr(); + + /** + * Gets the name of the current workflow. + * + * @return the name of the current workflow + */ String getName(); + /** + * Gets the instance ID of the current workflow. + * + * @return the instance ID of the current workflow + */ String getInstanceId(); - void complete(Object o); + /** + * Completes the current workflow. + * + * @param output the serializable output of the completed Workflow. + */ + void complete(Object output); + /** + * Waits for an event to be raised with name and returns the event data. + * + * @param eventName The name of the event to wait for. Event names are case-insensitive. + * External event names can be reused any number of times; they are not + * required to be unique. + * @param timeout The amount of time to wait before cancelling the external event task. + * @return Asynchronous task to {@code await()}. + */ Task waitForExternalEvent(String eventName, Duration timeout); } diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowRuntime.java b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowRuntime.java index 22c1145c8f..ff1993ebc1 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowRuntime.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowRuntime.java @@ -15,6 +15,7 @@ import com.microsoft.durabletask.DurableTaskGrpcWorker; import com.microsoft.durabletask.DurableTaskGrpcWorkerBuilder; +import io.dapr.config.Properties; /** * Contains methods to register workflows and activities. @@ -31,7 +32,12 @@ private WorkflowRuntime() throws IllegalStateException { throw new IllegalStateException("WorkflowRuntime should only be constructed once"); } - this.builder = new DurableTaskGrpcWorkerBuilder(); + int port = Properties.GRPC_PORT.get(); + if (port <= 0) { + throw new IllegalStateException("Invalid port."); + } + + this.builder = new DurableTaskGrpcWorkerBuilder().port(port); } /** @@ -67,13 +73,23 @@ public void registerWorkflow(Class clazz) { } /** - * Start the Workflow runtime. + * Start the Workflow runtime processing items on a non-blocking + * background thread indefinitely or until that thread is interrupted. */ public void start() { this.worker = this.builder.build(); this.worker.start(); } + /** + * Start the Workflow runtime processing items on the current thread + * and block indefinitely or until that thread is interrupted. + */ + public void startAndBlock() { + this.worker = this.builder.build(); + this.worker.startAndBlock(); + } + /** * {@inheritDoc} */ @@ -81,6 +97,7 @@ public void start() { public void close() { if (this.worker != null) { this.worker.close(); + this.worker = null; } } } \ No newline at end of file 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 742b15c8a0..57bfd1f31a 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 @@ -22,12 +22,12 @@ import java.lang.reflect.Constructor; import java.util.Arrays; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.mockito.Mockito.*; public class DaprWorkflowClientTest { - private DaprWorkflowClient client; - private static Constructor constructor; + private DaprWorkflowClient client; private DurableTaskClient mockInnerClient; private ManagedChannel mockGrpcChannel; @@ -45,15 +45,46 @@ public static void beforeAll() { public void setUp() throws Exception { mockInnerClient = mock(DurableTaskClient.class); mockGrpcChannel = mock(ManagedChannel.class); + when(mockGrpcChannel.shutdown()).thenReturn(mockGrpcChannel); + client = constructor.newInstance(mockInnerClient, mockGrpcChannel); } @Test - public void scheduleNewWorkflow() { - String expectedArgument = "TestWorkflow"; + public void EmptyConstructor() { + assertDoesNotThrow(DaprWorkflowClient::new); + } + + @Test + public void scheduleNewWorkflowWithArgName() { + String expectedName = "TestWorkflow"; + + client.scheduleNewWorkflow(expectedName); + + verify(mockInnerClient, times(1)).scheduleNewOrchestrationInstance(expectedName); + } + + @Test + public void scheduleNewWorkflowWithArgsNameInput() { + String expectedName = "TestWorkflow"; + Object expectedInput = new Object(); + + client.scheduleNewWorkflow(expectedName, expectedInput); + + verify(mockInnerClient, times(1)) + .scheduleNewOrchestrationInstance(expectedName, expectedInput); + } + + @Test + public void scheduleNewWorkflowWithArgsNameInputInstance() { + String expectedName = "TestWorkflow"; + Object expectedInput = new Object(); + String expectedInstanceId = "myTestInstance123"; + + client.scheduleNewWorkflow(expectedName, expectedInput, expectedInstanceId); - client.scheduleNewWorkflow(expectedArgument); - verify(mockInnerClient, times(1)).scheduleNewOrchestrationInstance(expectedArgument); + verify(mockInnerClient, times(1)) + .scheduleNewOrchestrationInstance(expectedName, expectedInput, expectedInstanceId); } @Test @@ -65,7 +96,7 @@ public void terminateWorkflow() { } @Test - public void close() { + public void close() throws InterruptedException { client.close(); verify(mockInnerClient, times(1)).close(); verify(mockGrpcChannel, times(1)).shutdown(); diff --git a/sdk-workflows/src/test/java/io/dapr/workflows/runtime/DaprWorkflowContextImplTest.java b/sdk-workflows/src/test/java/io/dapr/workflows/runtime/DaprWorkflowContextImplTest.java index 0d084a6d98..f3d8674a48 100644 --- a/sdk-workflows/src/test/java/io/dapr/workflows/runtime/DaprWorkflowContextImplTest.java +++ b/sdk-workflows/src/test/java/io/dapr/workflows/runtime/DaprWorkflowContextImplTest.java @@ -17,6 +17,7 @@ import org.junit.Test; import org.junit.Before; +import java.io.PrintStream; import java.time.Duration; import static org.mockito.Mockito.*; @@ -52,6 +53,15 @@ public void waitForExternalEventTest() { verify(mockInnerContext, times(1)).waitForExternalEvent(expectedEvent, expectedDuration); } + @Test + public void waitForExternalEventTimeoutTest() { + String expectedEvent = "TestEvent"; + Duration expectedDuration = Duration.ofSeconds(1); + + context.waitForExternalEvent(expectedEvent, expectedDuration); + verify(mockInnerContext, times(1)).waitForExternalEvent(expectedEvent, expectedDuration); + } + @Test(expected = IllegalArgumentException.class) public void DaprWorkflowContextWithEmptyInnerContext() { context = new DaprWorkflowContextImpl(null); @@ -62,4 +72,64 @@ public void completeTest() { context.complete(null); verify(mockInnerContext, times(1)).complete(null); } + + @Test + public void getOutReplayingTest() { + PrintStream mockOut = mock(PrintStream.class); + PrintStream originalOut = System.out; + System.setOut(mockOut); + when(mockInnerContext.getIsReplaying()).thenReturn(true); + DaprWorkflowContextImpl testContext = new DaprWorkflowContextImpl(mockInnerContext); + + String expectedArg = "test print"; + testContext.getOut().println(expectedArg); + + verify(mockOut, times(0)).println(any(String.class)); + System.setOut(originalOut); + } + + @Test + public void getOutFirstTimeTest() { + PrintStream mockOut = mock(PrintStream.class); + PrintStream originalOut = System.out; + System.setOut(mockOut); + when(mockInnerContext.getIsReplaying()).thenReturn(false); + DaprWorkflowContextImpl testContext = new DaprWorkflowContextImpl(mockInnerContext); + + String expectedArg = "test print"; + testContext.getOut().println(expectedArg); + + verify(mockOut, times(1)).println(expectedArg); + System.setOut(originalOut); + } + + @Test + public void getErrReplayingTest() { + PrintStream mockErr = mock(PrintStream.class); + PrintStream originalErr = System.err; + System.setOut(mockErr); + when(mockInnerContext.getIsReplaying()).thenReturn(true); + DaprWorkflowContextImpl testContext = new DaprWorkflowContextImpl(mockInnerContext); + + String expectedArg = "test print"; + testContext.getErr().println(expectedArg); + + verify(mockErr, times(0)).println(any(String.class)); + System.setOut(originalErr); + } + + @Test + public void getErrFirstTimeTest() { + PrintStream mockErr = mock(PrintStream.class); + PrintStream originalErr = System.err; + System.setErr(mockErr); + when(mockInnerContext.getIsReplaying()).thenReturn(false); + DaprWorkflowContextImpl testContext = new DaprWorkflowContextImpl(mockInnerContext); + + String expectedArg = "test print"; + testContext.getErr().println(expectedArg); + + verify(mockErr, times(1)).println(expectedArg); + System.setOut(originalErr); + } } diff --git a/sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowRuntimeTest.java b/sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowRuntimeTest.java index cd0d4b688a..2a3ac846f1 100644 --- a/sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowRuntimeTest.java +++ b/sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowRuntimeTest.java @@ -22,14 +22,10 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; public class WorkflowRuntimeTest { - - private static Constructor constructor; - private DurableTaskGrpcWorkerBuilder mockWorkerBuilder; - - public static class TestWorkflow extends Workflow{ - @Override - public void run(WorkflowContext ctx) { } - } + public static class TestWorkflow extends Workflow { + @Override + public void run(WorkflowContext ctx) { } + } @Test public void registerValidWorkflowClass() { @@ -37,7 +33,7 @@ public void registerValidWorkflowClass() { } @Test - public void startAndClose() { + public void startTest() { assertDoesNotThrow(() -> { WorkflowRuntime.getInstance().start(); WorkflowRuntime.getInstance().close(); From 3cfd8c107a11958470359ec174f84d4d0705104b Mon Sep 17 00:00:00 2001 From: Bill DeRusha <444835+bderusha@users.noreply.github.com> Date: Thu, 1 Jun 2023 12:57:18 -0400 Subject: [PATCH 05/18] Update workflow example README Signed-off-by: Bill DeRusha <444835+bderusha@users.noreply.github.com> --- examples/src/main/java/io/dapr/examples/workflows/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/src/main/java/io/dapr/examples/workflows/README.md b/examples/src/main/java/io/dapr/examples/workflows/README.md index 81ce80951a..c8b07593bd 100644 --- a/examples/src/main/java/io/dapr/examples/workflows/README.md +++ b/examples/src/main/java/io/dapr/examples/workflows/README.md @@ -64,7 +64,7 @@ This application uses `WorkflowRuntime.getInstance().registerWorkflow()` in orde Now, execute the following script in order to run DemoWorkflowWorker: ```sh -dapr run --app-id demoworkflowworker --resources-path ./components/workflows --dapr-grpc-port 4001 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.DemoWorkflowWorker +dapr run --app-id demoworkflowworker --resources-path ./components/workflows --dapr-grpc-port 50001 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.DemoWorkflowWorker ``` ### Running the Workflow client @@ -74,5 +74,5 @@ The `DemoWorkflowClient` starts instances of workflows that have been registered With the DemoWorkflowWorker running, use the follow command to start the workflow with the DemoWorkflowClient: ```sh -dapr run --app-id demoworkflowclient --resources-path ./components/workflows --dapr-grpc-port 4001 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.DemoWorkflowClient +java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.workflows.DemoWorkflowClient ``` From a6cfda9eca6832e978a8d70105e0c693ba9f80de Mon Sep 17 00:00:00 2001 From: Bill DeRusha <444835+bderusha@users.noreply.github.com> Date: Fri, 2 Jun 2023 11:33:24 -0400 Subject: [PATCH 06/18] Address PR feedback Signed-off-by: Bill DeRusha <444835+bderusha@users.noreply.github.com> --- examples/pom.xml | 2 +- .../dapr/examples/workflows/DemoWorkflow.java | 12 ++-- .../workflows/DemoWorkflowClient.java | 5 +- sdk-workflows/pom.xml | 10 +-- .../workflows/client/DaprWorkflowClient.java | 24 ++++--- .../runtime/DaprWorkflowContextImpl.java | 31 +++----- .../runtime/OrchestratorWrapper.java | 2 +- .../workflows/runtime/WorkflowContext.java | 18 ++--- .../workflows/runtime/WorkflowRuntime.java | 2 +- .../client/DaprWorkflowClientTest.java | 22 ++++-- .../runtime/DaprWorkflowContextImplTest.java | 70 ++++--------------- 11 files changed, 70 insertions(+), 128 deletions(-) diff --git a/examples/pom.xml b/examples/pom.xml index b831afbedc..99c7d9bb7d 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -121,7 +121,7 @@ io.dapr dapr-sdk-workflows - ${project.version} + 0.1.0-alpha-SNAPSHOT com.microsoft diff --git a/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflow.java b/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflow.java index b5c9c7cc36..deae954950 100644 --- a/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflow.java +++ b/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflow.java @@ -27,15 +27,15 @@ public class DemoWorkflow extends Workflow { @Override public void run(WorkflowContext ctx) { - ctx.getOut().println("Starting Workflow: " + ctx.getName()); - ctx.getOut().println("Instance ID: " + ctx.getInstanceId()); - ctx.getOut().println("Waiting for event: 'myEvent'..."); + ctx.getLogger().info("Starting Workflow: " + ctx.getName()); + ctx.getLogger().info("Instance ID: " + ctx.getInstanceId()); + ctx.getLogger().info("Waiting for event: 'myEvent'..."); try { ctx.waitForExternalEvent("myEvent", Duration.ofSeconds(10)).await(); - ctx.getOut().println("Received!"); + ctx.getLogger().info("Received!"); } catch (TaskCanceledException e) { - ctx.getErr().println("Timed out"); - ctx.getErr().println(e.getMessage()); + ctx.getLogger().warn("Timed out"); + ctx.getLogger().warn(e.getMessage()); } ctx.complete("finished"); } diff --git a/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowClient.java b/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowClient.java index 6d91aa54bf..cf439c969f 100644 --- a/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowClient.java +++ b/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowClient.java @@ -32,8 +32,7 @@ public static void main(String[] args) throws InterruptedException { try (client) { System.out.println("*****"); - String workflowName = DemoWorkflow.class.getCanonicalName(); - String instanceId = client.scheduleNewWorkflow(workflowName); + String instanceId = client.scheduleNewWorkflow(DemoWorkflow.class); System.out.printf("Started new workflow instance with random ID: %s%n", instanceId); System.out.println("Sleep and allow this workflow instance to timeout..."); @@ -41,7 +40,7 @@ public static void main(String[] args) throws InterruptedException { System.out.println("*****"); String instanceToTerminateId = "terminateMe"; - client.scheduleNewWorkflow(workflowName, null, instanceToTerminateId); + client.scheduleNewWorkflow(DemoWorkflow.class, null, instanceToTerminateId); System.out.printf("Started new workflow instance with specified ID: %s%n", instanceToTerminateId); TimeUnit.SECONDS.sleep(5); diff --git a/sdk-workflows/pom.xml b/sdk-workflows/pom.xml index 46ea6a18a9..5fa06d8ba3 100644 --- a/sdk-workflows/pom.xml +++ b/sdk-workflows/pom.xml @@ -12,7 +12,7 @@ dapr-sdk-workflows jar - 1.9.0-SNAPSHOT + 0.1.0-alpha-SNAPSHOT dapr-sdk-workflows SDK for Workflows on Dapr @@ -25,7 +25,7 @@ io.dapr dapr-sdk - ${project.version} + ${parent.version} junit @@ -43,12 +43,6 @@ 5.5.2 test - - commons-cli - commons-cli - 1.4 - test - com.microsoft durabletask-client 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 7123ed28ad..30605642ec 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,6 +17,7 @@ import com.microsoft.durabletask.DurableTaskGrpcClientBuilder; import io.dapr.config.Properties; import io.dapr.utils.Version; +import io.dapr.workflows.runtime.Workflow; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; @@ -77,7 +78,7 @@ private static DurableTaskClient createDurableTaskClient(ManagedChannel grpcChan private static ManagedChannel createGrpcChannel() throws IllegalStateException { int port = Properties.GRPC_PORT.get(); if (port <= 0) { - throw new IllegalStateException("Invalid port."); + throw new IllegalStateException(String.format("Invalid port, %s. Must greater than 0", port)); } return ManagedChannelBuilder.forAddress(Properties.SIDECAR_IP.get(), port) @@ -89,34 +90,37 @@ private static ManagedChannel createGrpcChannel() throws IllegalStateException { /** * Schedules a new workflow using DurableTask client. * - * @param workflowName name of workflow to start. + * @param any Workflow type + * @param clazz Class extending Workflow to start an instance of. * @return the randomly-generated instance ID for new Workflow instance. */ - public String scheduleNewWorkflow(String workflowName) { - return this.innerClient.scheduleNewOrchestrationInstance(workflowName); + public String scheduleNewWorkflow(Class clazz) { + return this.innerClient.scheduleNewOrchestrationInstance(clazz.getCanonicalName()); } /** * Schedules a new workflow using DurableTask client. * - * @param workflowName name of workflow to start. + * @param any Workflow type + * @param clazz Class extending Workflow to start an instance of. * @param input the input to pass to the scheduled orchestration instance. Must be serializable. * @return the randomly-generated instance ID for new Workflow instance. */ - public String scheduleNewWorkflow(String workflowName, Object input) { - return this.innerClient.scheduleNewOrchestrationInstance(workflowName, input); + public String scheduleNewWorkflow(Class clazz, Object input) { + return this.innerClient.scheduleNewOrchestrationInstance(clazz.getCanonicalName(), input); } /** * Schedules a new workflow using DurableTask client. * - * @param workflowName name of workflow to start. + * @param any Workflow type + * @param clazz Class extending Workflow to start an instance of. * @param input the input to pass to the scheduled orchestration instance. Must be serializable. * @param instanceId the unique ID of the orchestration instance to schedule * @return the instanceId parameter value. */ - public String scheduleNewWorkflow(String workflowName, Object input, String instanceId) { - return this.innerClient.scheduleNewOrchestrationInstance(workflowName, input, instanceId); + public String scheduleNewWorkflow(Class clazz, Object input, String instanceId) { + return this.innerClient.scheduleNewOrchestrationInstance(clazz.getCanonicalName(), input, instanceId); } /** diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DaprWorkflowContextImpl.java b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DaprWorkflowContextImpl.java index 29c9408fd6..d0e1faa933 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DaprWorkflowContextImpl.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DaprWorkflowContextImpl.java @@ -15,51 +15,40 @@ import com.microsoft.durabletask.Task; import com.microsoft.durabletask.TaskOrchestrationContext; - -import java.io.OutputStream; -import java.io.PrintStream; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.helpers.NOPLogger; import java.time.Duration; public class DaprWorkflowContextImpl implements WorkflowContext { private final TaskOrchestrationContext innerContext; - private final PrintStream dummyStream; + private final Logger logger; /** * Constructor for DaprWorkflowContextImpl. * * @param context TaskOrchestrationContext + * @param logger optional Logger * @throws IllegalArgumentException if context is null */ - public DaprWorkflowContextImpl(TaskOrchestrationContext context) throws IllegalArgumentException { + public DaprWorkflowContextImpl(TaskOrchestrationContext context, Logger logger) throws IllegalArgumentException { if (context == null) { throw new IllegalArgumentException("Inner context cannot be null"); } else { this.innerContext = context; } - this.dummyStream = new PrintStream(new OutputStream() { - public void write(int b) { - } - }); - } - /** - * {@inheritDoc} - */ - public PrintStream getOut() { - if (this.innerContext.getIsReplaying()) { - return this.dummyStream; - } - return System.out; + this.logger = logger == null ? LoggerFactory.getLogger(WorkflowContext.class) : logger; } /** * {@inheritDoc} */ - public PrintStream getErr() { + public Logger getLogger() { if (this.innerContext.getIsReplaying()) { - return this.dummyStream; + return NOPLogger.NOP_LOGGER; } - return System.err; + return this.logger; } /** diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/OrchestratorWrapper.java b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/OrchestratorWrapper.java index 44f6885934..83c4263719 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/OrchestratorWrapper.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/OrchestratorWrapper.java @@ -41,7 +41,7 @@ public TaskOrchestration create() { return ctx -> { try { T workflow = this.workflowConstructor.newInstance(); - workflow.run(new DaprWorkflowContextImpl(ctx)); + workflow.run(new DaprWorkflowContextImpl(ctx, null)); } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { throw new RuntimeException(e); } diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowContext.java b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowContext.java index 45cb81df19..690a93c200 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowContext.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowContext.java @@ -14,6 +14,7 @@ package io.dapr.workflows.runtime; import com.microsoft.durabletask.Task; +import org.slf4j.Logger; import java.io.PrintStream; import java.time.Duration; @@ -26,22 +27,13 @@ public interface WorkflowContext { /** - * Get the stdout PrintStream set by {@code System.out} - * only when {@code isReplaying} is false. Otherwise return - * a dummy PrintStream. + * Get a logger only when {@code isReplaying} is false. + * Otherwise, return a NOP (no operation) logger. * - * @return PrintStream + * @return Logger */ - PrintStream getOut(); + Logger getLogger(); - /** - * Get the stderr PrintStream set by {@code System.err} - * only when {@code isReplaying} is false. Otherwise return - * a dummy PrintStream. - * - * @return PrintStream - */ - public PrintStream getErr(); /** * Gets the name of the current workflow. diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowRuntime.java b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowRuntime.java index ff1993ebc1..0980b32564 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowRuntime.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowRuntime.java @@ -34,7 +34,7 @@ private WorkflowRuntime() throws IllegalStateException { int port = Properties.GRPC_PORT.get(); if (port <= 0) { - throw new IllegalStateException("Invalid port."); + throw new IllegalStateException(String.format("Invalid port, %s. Must greater than 0", port)); } this.builder = new DurableTaskGrpcWorkerBuilder().port(port); 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 57bfd1f31a..b9a32a4832 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 @@ -14,6 +14,8 @@ package io.dapr.workflows.client; import com.microsoft.durabletask.DurableTaskClient; +import io.dapr.workflows.runtime.Workflow; +import io.dapr.workflows.runtime.WorkflowContext; import io.grpc.ManagedChannel; import org.junit.Before; import org.junit.BeforeClass; @@ -31,6 +33,12 @@ public class DaprWorkflowClientTest { private DurableTaskClient mockInnerClient; private ManagedChannel mockGrpcChannel; + public class TestWorkflow extends Workflow { + @Override + public void run(WorkflowContext ctx) { + } + } + @BeforeClass public static void beforeAll() { constructor = @@ -57,19 +65,19 @@ public void EmptyConstructor() { @Test public void scheduleNewWorkflowWithArgName() { - String expectedName = "TestWorkflow"; + String expectedName = TestWorkflow.class.getCanonicalName(); - client.scheduleNewWorkflow(expectedName); + client.scheduleNewWorkflow(TestWorkflow.class); verify(mockInnerClient, times(1)).scheduleNewOrchestrationInstance(expectedName); } @Test public void scheduleNewWorkflowWithArgsNameInput() { - String expectedName = "TestWorkflow"; + String expectedName = TestWorkflow.class.getCanonicalName(); Object expectedInput = new Object(); - client.scheduleNewWorkflow(expectedName, expectedInput); + client.scheduleNewWorkflow(TestWorkflow.class, expectedInput); verify(mockInnerClient, times(1)) .scheduleNewOrchestrationInstance(expectedName, expectedInput); @@ -77,11 +85,11 @@ public void scheduleNewWorkflowWithArgsNameInput() { @Test public void scheduleNewWorkflowWithArgsNameInputInstance() { - String expectedName = "TestWorkflow"; + String expectedName = TestWorkflow.class.getCanonicalName(); Object expectedInput = new Object(); String expectedInstanceId = "myTestInstance123"; - client.scheduleNewWorkflow(expectedName, expectedInput, expectedInstanceId); + client.scheduleNewWorkflow(TestWorkflow.class, expectedInput, expectedInstanceId); verify(mockInnerClient, times(1)) .scheduleNewOrchestrationInstance(expectedName, expectedInput, expectedInstanceId); @@ -89,7 +97,7 @@ public void scheduleNewWorkflowWithArgsNameInputInstance() { @Test public void terminateWorkflow() { - String expectedArgument = "TestWorkflow"; + String expectedArgument = "TestWorkflowInstanceId"; client.terminateWorkflow(expectedArgument, null); verify(mockInnerClient, times(1)).terminate(expectedArgument, null); diff --git a/sdk-workflows/src/test/java/io/dapr/workflows/runtime/DaprWorkflowContextImplTest.java b/sdk-workflows/src/test/java/io/dapr/workflows/runtime/DaprWorkflowContextImplTest.java index f3d8674a48..d2129a2e5a 100644 --- a/sdk-workflows/src/test/java/io/dapr/workflows/runtime/DaprWorkflowContextImplTest.java +++ b/sdk-workflows/src/test/java/io/dapr/workflows/runtime/DaprWorkflowContextImplTest.java @@ -16,6 +16,7 @@ import com.microsoft.durabletask.TaskOrchestrationContext; import org.junit.Test; import org.junit.Before; +import org.slf4j.Logger; import java.io.PrintStream; import java.time.Duration; @@ -29,7 +30,7 @@ public class DaprWorkflowContextImplTest { @Before public void setUp() { mockInnerContext = mock(TaskOrchestrationContext.class); - context = new DaprWorkflowContextImpl(mockInnerContext); + context = new DaprWorkflowContextImpl(mockInnerContext, null); } @Test @@ -53,18 +54,9 @@ public void waitForExternalEventTest() { verify(mockInnerContext, times(1)).waitForExternalEvent(expectedEvent, expectedDuration); } - @Test - public void waitForExternalEventTimeoutTest() { - String expectedEvent = "TestEvent"; - Duration expectedDuration = Duration.ofSeconds(1); - - context.waitForExternalEvent(expectedEvent, expectedDuration); - verify(mockInnerContext, times(1)).waitForExternalEvent(expectedEvent, expectedDuration); - } - @Test(expected = IllegalArgumentException.class) public void DaprWorkflowContextWithEmptyInnerContext() { - context = new DaprWorkflowContextImpl(null); + context = new DaprWorkflowContextImpl(null, null); } @Test @@ -74,62 +66,26 @@ public void completeTest() { } @Test - public void getOutReplayingTest() { - PrintStream mockOut = mock(PrintStream.class); - PrintStream originalOut = System.out; - System.setOut(mockOut); - when(mockInnerContext.getIsReplaying()).thenReturn(true); - DaprWorkflowContextImpl testContext = new DaprWorkflowContextImpl(mockInnerContext); - - String expectedArg = "test print"; - testContext.getOut().println(expectedArg); - - verify(mockOut, times(0)).println(any(String.class)); - System.setOut(originalOut); - } - - @Test - public void getOutFirstTimeTest() { - PrintStream mockOut = mock(PrintStream.class); - PrintStream originalOut = System.out; - System.setOut(mockOut); - when(mockInnerContext.getIsReplaying()).thenReturn(false); - DaprWorkflowContextImpl testContext = new DaprWorkflowContextImpl(mockInnerContext); - - String expectedArg = "test print"; - testContext.getOut().println(expectedArg); - - verify(mockOut, times(1)).println(expectedArg); - System.setOut(originalOut); - } - - @Test - public void getErrReplayingTest() { - PrintStream mockErr = mock(PrintStream.class); - PrintStream originalErr = System.err; - System.setOut(mockErr); + public void getLoggerReplayingTest() { + Logger mockLogger = mock(Logger.class); when(mockInnerContext.getIsReplaying()).thenReturn(true); - DaprWorkflowContextImpl testContext = new DaprWorkflowContextImpl(mockInnerContext); + DaprWorkflowContextImpl testContext = new DaprWorkflowContextImpl(mockInnerContext, mockLogger); String expectedArg = "test print"; - testContext.getErr().println(expectedArg); + testContext.getLogger().info(expectedArg); - verify(mockErr, times(0)).println(any(String.class)); - System.setOut(originalErr); + verify(mockLogger, times(0)).info(any(String.class)); } @Test - public void getErrFirstTimeTest() { - PrintStream mockErr = mock(PrintStream.class); - PrintStream originalErr = System.err; - System.setErr(mockErr); + public void getLoggerFirstTimeTest() { + Logger mockLogger = mock(Logger.class); when(mockInnerContext.getIsReplaying()).thenReturn(false); - DaprWorkflowContextImpl testContext = new DaprWorkflowContextImpl(mockInnerContext); + DaprWorkflowContextImpl testContext = new DaprWorkflowContextImpl(mockInnerContext, mockLogger); String expectedArg = "test print"; - testContext.getErr().println(expectedArg); + testContext.getLogger().info(expectedArg); - verify(mockErr, times(1)).println(expectedArg); - System.setOut(originalErr); + verify(mockLogger, times(1)).info(expectedArg); } } From 1ed1940d5ad3372d0dbb5ab56007973aeb2e81da Mon Sep 17 00:00:00 2001 From: Bill DeRusha <444835+bderusha@users.noreply.github.com> Date: Fri, 2 Jun 2023 11:59:03 -0400 Subject: [PATCH 07/18] fixup deprecated pom.xml variable Signed-off-by: Bill DeRusha <444835+bderusha@users.noreply.github.com> --- sdk-workflows/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk-workflows/pom.xml b/sdk-workflows/pom.xml index 5fa06d8ba3..c7338c5dfe 100644 --- a/sdk-workflows/pom.xml +++ b/sdk-workflows/pom.xml @@ -25,7 +25,7 @@ io.dapr dapr-sdk - ${parent.version} + ${project.parent.version} junit From 452a4d9449926a0a42208d50a72e77d2f02e2e9a Mon Sep 17 00:00:00 2001 From: Bill DeRusha <444835+bderusha@users.noreply.github.com> Date: Tue, 6 Jun 2023 11:18:02 -0400 Subject: [PATCH 08/18] Updates based on PR feedback Signed-off-by: Bill DeRusha <444835+bderusha@users.noreply.github.com> --- .../runtime/DaprWorkflowContextImpl.java | 22 +++++++++++++++---- .../runtime/OrchestratorWrapper.java | 10 ++++++--- .../workflows/runtime/WorkflowRuntime.java | 22 +++++++++---------- .../runtime/DaprWorkflowContextImplTest.java | 2 +- 4 files changed, 37 insertions(+), 19 deletions(-) diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DaprWorkflowContextImpl.java b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DaprWorkflowContextImpl.java index d0e1faa933..3460f2be68 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DaprWorkflowContextImpl.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DaprWorkflowContextImpl.java @@ -28,17 +28,31 @@ public class DaprWorkflowContextImpl implements WorkflowContext { * Constructor for DaprWorkflowContextImpl. * * @param context TaskOrchestrationContext - * @param logger optional Logger * @throws IllegalArgumentException if context is null */ + public DaprWorkflowContextImpl(TaskOrchestrationContext context) throws IllegalArgumentException { + this(context, LoggerFactory.getLogger(WorkflowContext.class)); + } + + /** + * Constructor for DaprWorkflowContextImpl. + * + * @param context TaskOrchestrationContext + * @param logger Logger + * @throws IllegalArgumentException if context or logger is null + */ public DaprWorkflowContextImpl(TaskOrchestrationContext context, Logger logger) throws IllegalArgumentException { if (context == null) { - throw new IllegalArgumentException("Inner context cannot be null"); + throw new IllegalArgumentException("Context cannot be null"); } else { this.innerContext = context; } - this.logger = logger == null ? LoggerFactory.getLogger(WorkflowContext.class) : logger; + if (logger == null) { + throw new IllegalArgumentException("Logger cannot be null"); + } else { + this.logger = logger; + } } /** @@ -76,6 +90,6 @@ public void complete(Object output) { * {@inheritDoc} */ public Task waitForExternalEvent(String eventName, Duration timeout) { - return innerContext.waitForExternalEvent(eventName, timeout); + return this.innerContext.waitForExternalEvent(eventName, timeout); } } diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/OrchestratorWrapper.java b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/OrchestratorWrapper.java index 83c4263719..e52777cee8 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/OrchestratorWrapper.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/OrchestratorWrapper.java @@ -26,9 +26,13 @@ class OrchestratorWrapper implements TaskOrchestrationFactor private final Constructor workflowConstructor; private final String name; - public OrchestratorWrapper(Class clazz) throws NoSuchMethodException { + public OrchestratorWrapper(Class clazz) { this.name = clazz.getCanonicalName(); - this.workflowConstructor = clazz.getDeclaredConstructor(); + try { + this.workflowConstructor = clazz.getDeclaredConstructor(); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } } @Override @@ -41,7 +45,7 @@ public TaskOrchestration create() { return ctx -> { try { T workflow = this.workflowConstructor.newInstance(); - workflow.run(new DaprWorkflowContextImpl(ctx, null)); + workflow.run(new DaprWorkflowContextImpl(ctx)); } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { throw new RuntimeException(e); } diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowRuntime.java b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowRuntime.java index 0980b32564..122dc78a1c 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowRuntime.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowRuntime.java @@ -63,13 +63,9 @@ public static WorkflowRuntime getInstance() { * @param clazz the class being registered */ public void registerWorkflow(Class clazz) { - try { - this.builder = this.builder.addOrchestration( - new OrchestratorWrapper<>(clazz) - ); - } catch (NoSuchMethodException e) { - throw new RuntimeException(e); - } + this.builder = this.builder.addOrchestration( + new OrchestratorWrapper<>(clazz) + ); } /** @@ -77,8 +73,10 @@ public void registerWorkflow(Class clazz) { * background thread indefinitely or until that thread is interrupted. */ public void start() { - this.worker = this.builder.build(); - this.worker.start(); + if (this.worker == null) { + this.worker = this.builder.build(); + this.worker.start(); + } } /** @@ -86,8 +84,10 @@ public void start() { * and block indefinitely or until that thread is interrupted. */ public void startAndBlock() { - this.worker = this.builder.build(); - this.worker.startAndBlock(); + if (this.worker == null) { + this.worker = this.builder.build(); + this.worker.startAndBlock(); + } } /** diff --git a/sdk-workflows/src/test/java/io/dapr/workflows/runtime/DaprWorkflowContextImplTest.java b/sdk-workflows/src/test/java/io/dapr/workflows/runtime/DaprWorkflowContextImplTest.java index d2129a2e5a..4cd628b5d5 100644 --- a/sdk-workflows/src/test/java/io/dapr/workflows/runtime/DaprWorkflowContextImplTest.java +++ b/sdk-workflows/src/test/java/io/dapr/workflows/runtime/DaprWorkflowContextImplTest.java @@ -30,7 +30,7 @@ public class DaprWorkflowContextImplTest { @Before public void setUp() { mockInnerContext = mock(TaskOrchestrationContext.class); - context = new DaprWorkflowContextImpl(mockInnerContext, null); + context = new DaprWorkflowContextImpl(mockInnerContext); } @Test From 9b2881b830eb6d87ca9de0912346f4b31c817576 Mon Sep 17 00:00:00 2001 From: Bill DeRusha <444835+bderusha@users.noreply.github.com> Date: Wed, 7 Jun 2023 09:55:46 -0400 Subject: [PATCH 09/18] Update pom files per feedback Signed-off-by: Bill DeRusha <444835+bderusha@users.noreply.github.com> --- examples/pom.xml | 5 ----- pom.xml | 24 ++---------------------- 2 files changed, 2 insertions(+), 27 deletions(-) diff --git a/examples/pom.xml b/examples/pom.xml index 99c7d9bb7d..969d0e0ad0 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -123,11 +123,6 @@ dapr-sdk-workflows 0.1.0-alpha-SNAPSHOT - - com.microsoft - durabletask-client - 1.1.0 - io.dapr dapr-sdk diff --git a/pom.xml b/pom.xml index 3386494c12..16a89e0ca0 100644 --- a/pom.xml +++ b/pom.xml @@ -7,27 +7,7 @@ io.dapr dapr-sdk-parent pom - - - org.junit.jupiter - junit-jupiter - - - junit - junit - - - junit - junit - - - com.microsoft - durabletask-client - 1.1.0 - compile - - - 1.9.0-SNAPSHOT + 1.9.0-SNAPSHOT dapr-sdk-parent SDK for Dapr. https://dapr.io @@ -108,7 +88,7 @@ org.jacoco jacoco-maven-plugin - 0.8.8 + 0.8.6 From edecaf60539bee29f880ef8c15b0713ef1c47cec Mon Sep 17 00:00:00 2001 From: Bill DeRusha <444835+bderusha@users.noreply.github.com> Date: Tue, 13 Jun 2023 15:01:51 -0400 Subject: [PATCH 10/18] Add unit testing example Signed-off-by: Bill DeRusha <444835+bderusha@users.noreply.github.com> --- .../unittesting/DaprWorkflowExampleTest.java | 99 +++++++++++++ .../io/dapr/examples/unittesting/README.md | 139 +++++++++++++++++- .../dapr/examples/workflows/DemoWorkflow.java | 1 - 3 files changed, 236 insertions(+), 3 deletions(-) create mode 100644 examples/src/main/java/io/dapr/examples/unittesting/DaprWorkflowExampleTest.java diff --git a/examples/src/main/java/io/dapr/examples/unittesting/DaprWorkflowExampleTest.java b/examples/src/main/java/io/dapr/examples/unittesting/DaprWorkflowExampleTest.java new file mode 100644 index 0000000000..3a9e73e9ab --- /dev/null +++ b/examples/src/main/java/io/dapr/examples/unittesting/DaprWorkflowExampleTest.java @@ -0,0 +1,99 @@ +/* + * 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.examples.unittesting; + +import com.microsoft.durabletask.Task; +import com.microsoft.durabletask.TaskCanceledException; +import io.dapr.workflows.runtime.Workflow; +import io.dapr.workflows.runtime.WorkflowContext; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.slf4j.Logger; + +import java.time.Duration; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; + +/** + * 1. Build and install jars: + * mvn clean install + * 2. cd [repo root]/examples + * 3. Run the test code: + * java -jar target/dapr-java-sdk-examples-exec.jar \ + * org.junit.platform.console.ConsoleLauncher --select-class=io.dapr.examples.unittesting.DaprWorkflowExampleTest + */ +public class DaprWorkflowExampleTest { + + private class DemoWorkflow extends Workflow { + + @Override + public void run(WorkflowContext ctx) { + String name = ctx.getName(); + String id = ctx.getInstanceId(); + try { + ctx.waitForExternalEvent("myEvent", Duration.ofSeconds(10)).await(); + } catch (TaskCanceledException e) { + ctx.getLogger().warn("Timed out"); + } + String output = name + ":" + id; + ctx.complete(output); + } + } + + @Test + public void testWorkflow() { + WorkflowContext mockContext = Mockito.mock(WorkflowContext.class); + String name = "DemoWorkflow"; + String id = "my-workflow-123"; + + Mockito.when(mockContext.getName()).thenReturn(name); + Mockito.when(mockContext.getInstanceId()).thenReturn(id); + Mockito.when(mockContext.waitForExternalEvent(anyString(),any(Duration.class))) + .thenReturn(Mockito.mock(Task.class)); + + new DemoWorkflow().run(mockContext); + + String expectedOutput = name + ":" + id; + Mockito.verify(mockContext, Mockito.times(1)).complete(expectedOutput); + } + + @Test + public void testWorkflowWaitForEventTimeout() { + WorkflowContext mockContext = Mockito.mock(WorkflowContext.class); + Logger mockLogger = Mockito.mock(Logger.class); + + Mockito.when(mockContext.getLogger()).thenReturn(mockLogger); + Mockito.when(mockContext.waitForExternalEvent(anyString(),any(Duration.class))) + .thenThrow(TaskCanceledException.class); + + new DemoWorkflow().run(mockContext); + + Mockito.verify(mockLogger, Mockito.times(1)).warn("Timed out"); + } + + @Test + public void testWorkflowWaitForEventNoTimeout() { + WorkflowContext mockContext = Mockito.mock(WorkflowContext.class); + Logger mockLogger = Mockito.mock(Logger.class); + + Mockito.when(mockContext.getLogger()).thenReturn(mockLogger); + Mockito.when(mockContext.waitForExternalEvent(anyString(),any(Duration.class))) + .thenReturn(Mockito.mock(Task.class)); + + new DemoWorkflow().run(mockContext); + + Mockito.verify(mockLogger, Mockito.times(0)).warn(anyString()); + } +} diff --git a/examples/src/main/java/io/dapr/examples/unittesting/README.md b/examples/src/main/java/io/dapr/examples/unittesting/README.md index cc1073a38e..7fd3190e87 100644 --- a/examples/src/main/java/io/dapr/examples/unittesting/README.md +++ b/examples/src/main/java/io/dapr/examples/unittesting/README.md @@ -33,7 +33,9 @@ cd examples ``` ### Understanding the code -This example will simulate an application code via the App class: + +#### Example App Test +This example, found in `DaprExampleTest.java`, will simulate an application code via the App class: ```java private static final class MyApp { @@ -115,7 +117,7 @@ The second test uses a mock implementation of the factory method and checks the ``` -### Running the example +##### Running the example + +Run this example with the following command: +```bash +java -jar target/dapr-java-sdk-examples-exec.jar org.junit.platform.console.ConsoleLauncher --select-class=io.dapr.examples.unittesting.DaprWorkflowExampleTest +``` + + + +After running, Junit should print the output as follows: + +```txt +╷ +├─ JUnit Jupiter ✔ +│ └─ DaprWorkflowExampleTest ✔ +│ ├─ testWorkflowWaitForEventTimeout() ✔ +│ ├─ testWorkflowWaitForEventNoTimeout() ✔ +│ └─ testWorkflow() ✔ +└─ JUnit Vintage ✔ + +Test run finished after 815 ms +[ 3 containers found ] +[ 0 containers skipped ] +[ 3 containers started ] +[ 0 containers aborted ] +[ 3 containers successful ] +[ 0 containers failed ] +[ 3 tests found ] +[ 0 tests skipped ] +[ 3 tests started ] +[ 0 tests aborted ] +[ 3 tests successful ] +[ 0 tests failed ] + ``` \ No newline at end of file diff --git a/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflow.java b/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflow.java index deae954950..1e967ca187 100644 --- a/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflow.java +++ b/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflow.java @@ -18,7 +18,6 @@ import io.dapr.workflows.runtime.WorkflowContext; import java.time.Duration; -import java.util.concurrent.TimeoutException; /** * Implementation of the DemoWorkflow for the server side. From 234ab448d55129ee249f80ab88c44d65d004e877 Mon Sep 17 00:00:00 2001 From: Bill DeRusha <444835+bderusha@users.noreply.github.com> Date: Wed, 21 Jun 2023 12:37:20 -0400 Subject: [PATCH 11/18] update pom Signed-off-by: Bill DeRusha <444835+bderusha@users.noreply.github.com> --- sdk-workflows/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk-workflows/pom.xml b/sdk-workflows/pom.xml index c7338c5dfe..b8b8f146d1 100644 --- a/sdk-workflows/pom.xml +++ b/sdk-workflows/pom.xml @@ -7,7 +7,7 @@ io.dapr dapr-sdk-parent - 1.9.0-SNAPSHOT + 1.10.0-SNAPSHOT dapr-sdk-workflows From 2fbc8050423c5257af96a1f08d580cf9e2394ec2 Mon Sep 17 00:00:00 2001 From: Bill DeRusha <444835+bderusha@users.noreply.github.com> Date: Thu, 22 Jun 2023 10:20:26 -0400 Subject: [PATCH 12/18] Fix dependency conflict Signed-off-by: Bill DeRusha <444835+bderusha@users.noreply.github.com> --- sdk-workflows/pom.xml | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/sdk-workflows/pom.xml b/sdk-workflows/pom.xml index b8b8f146d1..d728b6d45c 100644 --- a/sdk-workflows/pom.xml +++ b/sdk-workflows/pom.xml @@ -19,6 +19,7 @@ false 1.42.1 + 2.12.3 @@ -46,7 +47,32 @@ com.microsoft durabletask-client - 1.1.0 + 1.1.1 + + + + com.fasterxml.jackson.core + jackson-core + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + ${jackson.version} From 1cfae5eb8afc279620e71615f0fb23eebdf28c83 Mon Sep 17 00:00:00 2001 From: Bill DeRusha <444835+bderusha@users.noreply.github.com> Date: Wed, 2 Aug 2023 17:09:41 -0400 Subject: [PATCH 13/18] Use Mono and refactor GRPC managed channel builder Signed-off-by: Bill DeRusha <444835+bderusha@users.noreply.github.com> --- examples/pom.xml | 2 +- .../unittesting/DaprWorkflowExampleTest.java | 39 ++++++------ .../dapr/examples/workflows/DemoWorkflow.java | 28 +++++---- .../workflows/DemoWorkflowClient.java | 6 +- .../workflows/DemoWorkflowWorker.java | 2 +- sdk-workflows/pom.xml | 2 +- .../workflows/client/DaprWorkflowClient.java | 60 +++++++++---------- .../runtime/DaprWorkflowContextImpl.java | 26 +++++--- .../runtime/OrchestratorWrapper.java | 2 +- .../io/dapr/workflows/runtime/Workflow.java | 18 +++++- .../workflows/runtime/WorkflowContext.java | 18 +++--- .../workflows/runtime/WorkflowRuntime.java | 34 ++++------- .../dapr/workflows/runtime/WorkflowStub.java | 19 ++++++ .../client/DaprWorkflowClientTest.java | 13 ++-- .../runtime/DaprWorkflowContextImplTest.java | 15 +++-- .../runtime/OrchestratorWrapperTest.java | 11 ++-- .../runtime/WorkflowRuntimeTest.java | 7 +-- .../io/dapr/client/DaprClientBuilder.java | 30 +--------- .../main/java/io/dapr/utils/NetworkUtils.java | 31 ++++++++++ 19 files changed, 198 insertions(+), 165 deletions(-) create mode 100644 sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowStub.java diff --git a/examples/pom.xml b/examples/pom.xml index 69c227da7c..ac8e093c51 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -121,7 +121,7 @@ io.dapr dapr-sdk-workflows - 0.1.0-alpha-SNAPSHOT + 0.1.0-SNAPSHOT io.dapr diff --git a/examples/src/main/java/io/dapr/examples/unittesting/DaprWorkflowExampleTest.java b/examples/src/main/java/io/dapr/examples/unittesting/DaprWorkflowExampleTest.java index 3a9e73e9ab..25b1f5168c 100644 --- a/examples/src/main/java/io/dapr/examples/unittesting/DaprWorkflowExampleTest.java +++ b/examples/src/main/java/io/dapr/examples/unittesting/DaprWorkflowExampleTest.java @@ -13,13 +13,14 @@ package io.dapr.examples.unittesting; -import com.microsoft.durabletask.Task; import com.microsoft.durabletask.TaskCanceledException; import io.dapr.workflows.runtime.Workflow; import io.dapr.workflows.runtime.WorkflowContext; +import io.dapr.workflows.runtime.WorkflowStub; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.slf4j.Logger; +import reactor.core.publisher.Mono; import java.time.Duration; @@ -39,16 +40,18 @@ public class DaprWorkflowExampleTest { private class DemoWorkflow extends Workflow { @Override - public void run(WorkflowContext ctx) { - String name = ctx.getName(); - String id = ctx.getInstanceId(); - try { - ctx.waitForExternalEvent("myEvent", Duration.ofSeconds(10)).await(); - } catch (TaskCanceledException e) { - ctx.getLogger().warn("Timed out"); - } - String output = name + ":" + id; - ctx.complete(output); + public WorkflowStub create() { + return ctx -> { + String name = ctx.getName().block(); + String id = ctx.getInstanceId().block(); + try { + ctx.waitForExternalEvent("myEvent", Duration.ofSeconds(10)).block(); + } catch (TaskCanceledException e) { + ctx.getLogger().warn("Timed out"); + } + String output = name + ":" + id; + ctx.complete(output).block(); + }; } } @@ -58,12 +61,12 @@ public void testWorkflow() { String name = "DemoWorkflow"; String id = "my-workflow-123"; - Mockito.when(mockContext.getName()).thenReturn(name); - Mockito.when(mockContext.getInstanceId()).thenReturn(id); + Mockito.when(mockContext.getName()).thenReturn(Mono.just(name)); + Mockito.when(mockContext.getInstanceId()).thenReturn(Mono.just(id)); Mockito.when(mockContext.waitForExternalEvent(anyString(),any(Duration.class))) - .thenReturn(Mockito.mock(Task.class)); + .thenReturn(Mono.empty()); - new DemoWorkflow().run(mockContext); + new DemoWorkflow().create().run(mockContext); String expectedOutput = name + ":" + id; Mockito.verify(mockContext, Mockito.times(1)).complete(expectedOutput); @@ -78,7 +81,7 @@ public void testWorkflowWaitForEventTimeout() { Mockito.when(mockContext.waitForExternalEvent(anyString(),any(Duration.class))) .thenThrow(TaskCanceledException.class); - new DemoWorkflow().run(mockContext); + new DemoWorkflow().create().run(mockContext); Mockito.verify(mockLogger, Mockito.times(1)).warn("Timed out"); } @@ -90,9 +93,9 @@ public void testWorkflowWaitForEventNoTimeout() { Mockito.when(mockContext.getLogger()).thenReturn(mockLogger); Mockito.when(mockContext.waitForExternalEvent(anyString(),any(Duration.class))) - .thenReturn(Mockito.mock(Task.class)); + .thenReturn(Mono.empty()); - new DemoWorkflow().run(mockContext); + new DemoWorkflow().create().run(mockContext); Mockito.verify(mockLogger, Mockito.times(0)).warn(anyString()); } diff --git a/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflow.java b/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflow.java index 1e967ca187..2a85b49f55 100644 --- a/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflow.java +++ b/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflow.java @@ -15,7 +15,7 @@ import com.microsoft.durabletask.TaskCanceledException; import io.dapr.workflows.runtime.Workflow; -import io.dapr.workflows.runtime.WorkflowContext; +import io.dapr.workflows.runtime.WorkflowStub; import java.time.Duration; @@ -25,17 +25,19 @@ public class DemoWorkflow extends Workflow { @Override - public void run(WorkflowContext ctx) { - ctx.getLogger().info("Starting Workflow: " + ctx.getName()); - ctx.getLogger().info("Instance ID: " + ctx.getInstanceId()); - ctx.getLogger().info("Waiting for event: 'myEvent'..."); - try { - ctx.waitForExternalEvent("myEvent", Duration.ofSeconds(10)).await(); - ctx.getLogger().info("Received!"); - } catch (TaskCanceledException e) { - ctx.getLogger().warn("Timed out"); - ctx.getLogger().warn(e.getMessage()); - } - ctx.complete("finished"); + public WorkflowStub create() { + return ctx -> { + ctx.getLogger().info("Starting Workflow: " + ctx.getName().block()); + ctx.getLogger().info("Instance ID: " + ctx.getInstanceId().block()); + ctx.getLogger().info("Waiting for event: 'myEvent'..."); + try { + ctx.waitForExternalEvent("myEvent", Duration.ofSeconds(10)).block(); + ctx.getLogger().info("Received!"); + } catch (TaskCanceledException e) { + ctx.getLogger().warn("Timed out"); + ctx.getLogger().warn(e.getMessage()); + } + ctx.complete("finished").block(); + }; } } diff --git a/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowClient.java b/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowClient.java index cf439c969f..eebac25cda 100644 --- a/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowClient.java +++ b/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowClient.java @@ -32,7 +32,7 @@ public static void main(String[] args) throws InterruptedException { try (client) { System.out.println("*****"); - String instanceId = client.scheduleNewWorkflow(DemoWorkflow.class); + String instanceId = client.scheduleNewWorkflow(DemoWorkflow.class).block(); System.out.printf("Started new workflow instance with random ID: %s%n", instanceId); System.out.println("Sleep and allow this workflow instance to timeout..."); @@ -40,12 +40,12 @@ public static void main(String[] args) throws InterruptedException { System.out.println("*****"); String instanceToTerminateId = "terminateMe"; - client.scheduleNewWorkflow(DemoWorkflow.class, null, instanceToTerminateId); + client.scheduleNewWorkflow(DemoWorkflow.class, null, instanceToTerminateId).block(); System.out.printf("Started new workflow instance with specified ID: %s%n", instanceToTerminateId); TimeUnit.SECONDS.sleep(5); System.out.println("Terminate this workflow instance manually before the timeout is reached"); - client.terminateWorkflow(instanceToTerminateId, null); + client.terminateWorkflow(instanceToTerminateId, null).block(); System.out.println("*****"); } diff --git a/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowWorker.java b/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowWorker.java index 58d363b00a..991f0011a4 100644 --- a/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowWorker.java +++ b/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowWorker.java @@ -30,7 +30,7 @@ public static void main(String[] args) throws Exception { // Register the Workflow with the runtime. WorkflowRuntime.getInstance().registerWorkflow(DemoWorkflow.class); System.out.println("Start workflow runtime"); - WorkflowRuntime.getInstance().startAndBlock(); + WorkflowRuntime.getInstance().start().block(); System.exit(0); } } diff --git a/sdk-workflows/pom.xml b/sdk-workflows/pom.xml index d728b6d45c..f62e8a41ba 100644 --- a/sdk-workflows/pom.xml +++ b/sdk-workflows/pom.xml @@ -12,7 +12,7 @@ dapr-sdk-workflows jar - 0.1.0-alpha-SNAPSHOT + 0.1.0-SNAPSHOT dapr-sdk-workflows SDK for Workflows on Dapr 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 30605642ec..f0e34dbbfd 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 @@ -15,11 +15,10 @@ import com.microsoft.durabletask.DurableTaskClient; import com.microsoft.durabletask.DurableTaskGrpcClientBuilder; -import io.dapr.config.Properties; -import io.dapr.utils.Version; +import io.dapr.utils.NetworkUtils; import io.dapr.workflows.runtime.Workflow; import io.grpc.ManagedChannel; -import io.grpc.ManagedChannelBuilder; +import reactor.core.publisher.Mono; import javax.annotation.Nullable; import java.util.concurrent.TimeUnit; @@ -33,7 +32,7 @@ public class DaprWorkflowClient implements AutoCloseable { * Public constructor for DaprWorkflowClient. This layer constructs the GRPC Channel. */ public DaprWorkflowClient() { - this(createGrpcChannel()); + this(NetworkUtils.buildGrpcManagedChannel()); } /** @@ -69,33 +68,18 @@ private static DurableTaskClient createDurableTaskClient(ManagedChannel grpcChan .build(); } - /** - * Static method to create the GRPC Channel for the DurableTaskClient. - * - * @return a Managed GRPC channel. - * @throws IllegalStateException if the GRPC port is invalid. - */ - private static ManagedChannel createGrpcChannel() throws IllegalStateException { - int port = Properties.GRPC_PORT.get(); - if (port <= 0) { - throw new IllegalStateException(String.format("Invalid port, %s. Must greater than 0", port)); - } - - return ManagedChannelBuilder.forAddress(Properties.SIDECAR_IP.get(), port) - .usePlaintext() - .userAgent(Version.getSdkVersion()) - .build(); - } - /** * Schedules a new workflow using DurableTask client. * * @param any Workflow type * @param clazz Class extending Workflow to start an instance of. - * @return the randomly-generated instance ID for new Workflow instance. + * @return A Mono Plan of type String with the randomly-generated instance ID for new Workflow instance. */ - public String scheduleNewWorkflow(Class clazz) { - return this.innerClient.scheduleNewOrchestrationInstance(clazz.getCanonicalName()); + public Mono scheduleNewWorkflow(Class clazz) { + return Mono.create(it -> { + String response = this.innerClient.scheduleNewOrchestrationInstance(clazz.getCanonicalName()); + it.success(response); + }); } /** @@ -104,10 +88,13 @@ public String scheduleNewWorkflow(Class clazz) { * @param any Workflow type * @param clazz Class extending Workflow to start an instance of. * @param input the input to pass to the scheduled orchestration instance. Must be serializable. - * @return the randomly-generated instance ID for new Workflow instance. + * @return A Mono Plan of type String with the randomly-generated instance ID for new Workflow instance. */ - public String scheduleNewWorkflow(Class clazz, Object input) { - return this.innerClient.scheduleNewOrchestrationInstance(clazz.getCanonicalName(), input); + public Mono scheduleNewWorkflow(Class clazz, Object input) { + return Mono.create(it -> { + String response = this.innerClient.scheduleNewOrchestrationInstance(clazz.getCanonicalName(), input); + it.success(response); + }); } /** @@ -117,10 +104,13 @@ public String scheduleNewWorkflow(Class clazz, Object in * @param clazz Class extending Workflow to start an instance of. * @param input the input to pass to the scheduled orchestration instance. Must be serializable. * @param instanceId the unique ID of the orchestration instance to schedule - * @return the instanceId parameter value. + * @return A Mono Plan of type String with the instanceId parameter value. */ - public String scheduleNewWorkflow(Class clazz, Object input, String instanceId) { - return this.innerClient.scheduleNewOrchestrationInstance(clazz.getCanonicalName(), input, instanceId); + public Mono scheduleNewWorkflow(Class clazz, Object input, String instanceId) { + return Mono.create(it -> { + String response = this.innerClient.scheduleNewOrchestrationInstance(clazz.getCanonicalName(), input, instanceId); + it.success(response); + }); } /** @@ -128,9 +118,13 @@ public String scheduleNewWorkflow(Class clazz, Object in * * @param workflowInstanceId Workflow instance id to terminate. * @param output the optional output to set for the terminated orchestration instance. + * @return A Mono Plan of type Void. */ - public void terminateWorkflow(String workflowInstanceId, @Nullable Object output) { - this.innerClient.terminate(workflowInstanceId, output); + public Mono terminateWorkflow(String workflowInstanceId, @Nullable Object output) { + return Mono.create(it -> { + this.innerClient.terminate(workflowInstanceId, output); + it.success(); + }).then(); } /** diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DaprWorkflowContextImpl.java b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DaprWorkflowContextImpl.java index 3460f2be68..44926a76c2 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DaprWorkflowContextImpl.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DaprWorkflowContextImpl.java @@ -13,11 +13,13 @@ package io.dapr.workflows.runtime; -import com.microsoft.durabletask.Task; +import com.google.protobuf.Empty; import com.microsoft.durabletask.TaskOrchestrationContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.helpers.NOPLogger; +import reactor.core.publisher.Mono; + import java.time.Duration; public class DaprWorkflowContextImpl implements WorkflowContext { @@ -68,28 +70,34 @@ public Logger getLogger() { /** * {@inheritDoc} */ - public String getName() { - return this.innerContext.getName(); + public Mono getName() { + return Mono.create(it -> it.success(this.innerContext.getName())); } /** * {@inheritDoc} */ - public String getInstanceId() { - return this.innerContext.getInstanceId(); + public Mono getInstanceId() { + return Mono.create(it -> it.success(this.innerContext.getInstanceId())); } /** * {@inheritDoc} */ - public void complete(Object output) { - this.innerContext.complete(output); + public Mono complete(Object output) { + return Mono.create(it -> { + this.innerContext.complete(output); + it.success(); + }).then(); } /** * {@inheritDoc} */ - public Task waitForExternalEvent(String eventName, Duration timeout) { - return this.innerContext.waitForExternalEvent(eventName, timeout); + public Mono waitForExternalEvent(String eventName, Duration timeout) { + return Mono.create(it -> { + this.innerContext.waitForExternalEvent(eventName, timeout).await(); + it.success(); + }).then(); } } diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/OrchestratorWrapper.java b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/OrchestratorWrapper.java index e52777cee8..3a0ecd6412 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/OrchestratorWrapper.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/OrchestratorWrapper.java @@ -45,7 +45,7 @@ public TaskOrchestration create() { return ctx -> { try { T workflow = this.workflowConstructor.newInstance(); - workflow.run(new DaprWorkflowContextImpl(ctx)); + workflow.runAsync(new DaprWorkflowContextImpl(ctx)).block(); } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { throw new RuntimeException(e); } diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/Workflow.java b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/Workflow.java index 6460ac4a55..131a319d63 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/Workflow.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/Workflow.java @@ -13,7 +13,8 @@ package io.dapr.workflows.runtime; -import io.dapr.workflows.runtime.WorkflowContext; +import com.google.protobuf.Empty; +import reactor.core.publisher.Mono; /** * Common interface for workflow implementations. @@ -22,11 +23,24 @@ public abstract class Workflow { public Workflow(){ } + /** + * Executes the workflow logic. + * + * @return A WorkflowStub. + */ + public abstract WorkflowStub create(); + /** * Executes the workflow logic. * * @param ctx provides access to methods for scheduling durable tasks and getting information about the current * workflow instance. + * @return A Mono Plan of type Void. */ - public abstract void run(WorkflowContext ctx); + public Mono runAsync(WorkflowContext ctx) { + return Mono.create(it -> { + this.create().run(ctx); + it.success(); + }).then(); + } } diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowContext.java b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowContext.java index 690a93c200..674b88e917 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowContext.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowContext.java @@ -13,10 +13,9 @@ package io.dapr.workflows.runtime; -import com.microsoft.durabletask.Task; import org.slf4j.Logger; +import reactor.core.publisher.Mono; -import java.io.PrintStream; import java.time.Duration; /** @@ -38,23 +37,24 @@ public interface WorkflowContext { /** * Gets the name of the current workflow. * - * @return the name of the current workflow + * @return Asynchronous result with the name of the current workflow */ - String getName(); + Mono getName(); /** * Gets the instance ID of the current workflow. * - * @return the instance ID of the current workflow + * @return Asynchronous result with the instance ID of the current workflow */ - String getInstanceId(); + Mono getInstanceId(); /** * Completes the current workflow. * * @param output the serializable output of the completed Workflow. + * @return A Mono Plan of type Void. */ - void complete(Object output); + Mono complete(Object output); /** * Waits for an event to be raised with name and returns the event data. @@ -63,7 +63,7 @@ public interface WorkflowContext { * External event names can be reused any number of times; they are not * required to be unique. * @param timeout The amount of time to wait before cancelling the external event task. - * @return Asynchronous task to {@code await()}. + * @return A Mono Plan of type Void. */ - Task waitForExternalEvent(String eventName, Duration timeout); + Mono waitForExternalEvent(String eventName, Duration timeout); } diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowRuntime.java b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowRuntime.java index 122dc78a1c..a6c98bb2ba 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowRuntime.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowRuntime.java @@ -13,9 +13,11 @@ package io.dapr.workflows.runtime; +import com.google.protobuf.Empty; import com.microsoft.durabletask.DurableTaskGrpcWorker; import com.microsoft.durabletask.DurableTaskGrpcWorkerBuilder; -import io.dapr.config.Properties; +import io.dapr.utils.NetworkUtils; +import reactor.core.publisher.Mono; /** * Contains methods to register workflows and activities. @@ -27,17 +29,11 @@ public class WorkflowRuntime implements AutoCloseable { private DurableTaskGrpcWorker worker; private WorkflowRuntime() throws IllegalStateException { - if (instance != null) { throw new IllegalStateException("WorkflowRuntime should only be constructed once"); } - int port = Properties.GRPC_PORT.get(); - if (port <= 0) { - throw new IllegalStateException(String.format("Invalid port, %s. Must greater than 0", port)); - } - - this.builder = new DurableTaskGrpcWorkerBuilder().port(port); + this.builder = new DurableTaskGrpcWorkerBuilder().grpcChannel(NetworkUtils.buildGrpcManagedChannel()); } /** @@ -69,25 +65,17 @@ public void registerWorkflow(Class clazz) { } /** - * Start the Workflow runtime processing items on a non-blocking - * background thread indefinitely or until that thread is interrupted. - */ - public void start() { - if (this.worker == null) { - this.worker = this.builder.build(); - this.worker.start(); - } - } - - /** - * Start the Workflow runtime processing items on the current thread - * and block indefinitely or until that thread is interrupted. + * Start the Workflow runtime processing items. + * + * @return A Mono Plan of type Void. */ - public void startAndBlock() { + public Mono start() { if (this.worker == null) { this.worker = this.builder.build(); - this.worker.startAndBlock(); } + return Mono.create(it -> { + this.worker.start(); + }).then(); } /** diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowStub.java b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowStub.java new file mode 100644 index 0000000000..d10a29ff4f --- /dev/null +++ b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowStub.java @@ -0,0 +1,19 @@ +/* + * 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; + +@FunctionalInterface +public interface WorkflowStub { + void run(WorkflowContext ctx); +} 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 b9a32a4832..10fc731119 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 @@ -15,7 +15,7 @@ import com.microsoft.durabletask.DurableTaskClient; import io.dapr.workflows.runtime.Workflow; -import io.dapr.workflows.runtime.WorkflowContext; +import io.dapr.workflows.runtime.WorkflowStub; import io.grpc.ManagedChannel; import org.junit.Before; import org.junit.BeforeClass; @@ -35,7 +35,8 @@ public class DaprWorkflowClientTest { public class TestWorkflow extends Workflow { @Override - public void run(WorkflowContext ctx) { + public WorkflowStub create() { + return ctx -> { }; } } @@ -67,7 +68,7 @@ public void EmptyConstructor() { public void scheduleNewWorkflowWithArgName() { String expectedName = TestWorkflow.class.getCanonicalName(); - client.scheduleNewWorkflow(TestWorkflow.class); + client.scheduleNewWorkflow(TestWorkflow.class).block(); verify(mockInnerClient, times(1)).scheduleNewOrchestrationInstance(expectedName); } @@ -77,7 +78,7 @@ public void scheduleNewWorkflowWithArgsNameInput() { String expectedName = TestWorkflow.class.getCanonicalName(); Object expectedInput = new Object(); - client.scheduleNewWorkflow(TestWorkflow.class, expectedInput); + client.scheduleNewWorkflow(TestWorkflow.class, expectedInput).block(); verify(mockInnerClient, times(1)) .scheduleNewOrchestrationInstance(expectedName, expectedInput); @@ -89,7 +90,7 @@ public void scheduleNewWorkflowWithArgsNameInputInstance() { Object expectedInput = new Object(); String expectedInstanceId = "myTestInstance123"; - client.scheduleNewWorkflow(TestWorkflow.class, expectedInput, expectedInstanceId); + client.scheduleNewWorkflow(TestWorkflow.class, expectedInput, expectedInstanceId).block(); verify(mockInnerClient, times(1)) .scheduleNewOrchestrationInstance(expectedName, expectedInput, expectedInstanceId); @@ -99,7 +100,7 @@ public void scheduleNewWorkflowWithArgsNameInputInstance() { public void terminateWorkflow() { String expectedArgument = "TestWorkflowInstanceId"; - client.terminateWorkflow(expectedArgument, null); + client.terminateWorkflow(expectedArgument, null).block(); verify(mockInnerClient, times(1)).terminate(expectedArgument, null); } diff --git a/sdk-workflows/src/test/java/io/dapr/workflows/runtime/DaprWorkflowContextImplTest.java b/sdk-workflows/src/test/java/io/dapr/workflows/runtime/DaprWorkflowContextImplTest.java index 4cd628b5d5..23967e796b 100644 --- a/sdk-workflows/src/test/java/io/dapr/workflows/runtime/DaprWorkflowContextImplTest.java +++ b/sdk-workflows/src/test/java/io/dapr/workflows/runtime/DaprWorkflowContextImplTest.java @@ -13,12 +13,12 @@ package io.dapr.workflows.runtime; +import com.microsoft.durabletask.Task; import com.microsoft.durabletask.TaskOrchestrationContext; -import org.junit.Test; import org.junit.Before; +import org.junit.Test; import org.slf4j.Logger; -import java.io.PrintStream; import java.time.Duration; import static org.mockito.Mockito.*; @@ -35,22 +35,25 @@ public void setUp() { @Test public void getNameTest() { - context.getName(); + context.getName().block(); verify(mockInnerContext, times(1)).getName(); } @Test public void getInstanceIdTest() { - context.getInstanceId(); + context.getInstanceId().block(); verify(mockInnerContext, times(1)).getInstanceId(); } @Test public void waitForExternalEventTest() { + doReturn(mock(Task.class)) + .when(mockInnerContext).waitForExternalEvent(any(String.class), any(Duration.class)); + DaprWorkflowContextImpl testContext = new DaprWorkflowContextImpl(mockInnerContext); String expectedEvent = "TestEvent"; Duration expectedDuration = Duration.ofSeconds(1); - context.waitForExternalEvent(expectedEvent, expectedDuration); + testContext.waitForExternalEvent(expectedEvent, expectedDuration).block(); verify(mockInnerContext, times(1)).waitForExternalEvent(expectedEvent, expectedDuration); } @@ -61,7 +64,7 @@ public void DaprWorkflowContextWithEmptyInnerContext() { @Test public void completeTest() { - context.complete(null); + context.complete(null).block(); verify(mockInnerContext, times(1)).complete(null); } diff --git a/sdk-workflows/src/test/java/io/dapr/workflows/runtime/OrchestratorWrapperTest.java b/sdk-workflows/src/test/java/io/dapr/workflows/runtime/OrchestratorWrapperTest.java index 2ca86f4b64..463e335e0b 100644 --- a/sdk-workflows/src/test/java/io/dapr/workflows/runtime/OrchestratorWrapperTest.java +++ b/sdk-workflows/src/test/java/io/dapr/workflows/runtime/OrchestratorWrapperTest.java @@ -14,22 +14,19 @@ package io.dapr.workflows.runtime; -import com.microsoft.durabletask.DurableTaskGrpcWorkerBuilder; import com.microsoft.durabletask.TaskOrchestrationContext; -import com.microsoft.durabletask.TaskOrchestrationFactory; import org.junit.Assert; import org.junit.Test; -import java.lang.reflect.Constructor; - -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.mockito.Mockito.*; public class OrchestratorWrapperTest { public static class TestWorkflow extends Workflow{ @Override - public void run(WorkflowContext ctx) { - ctx.getInstanceId(); + public WorkflowStub create() { + return ctx -> { + ctx.getInstanceId().block(); + }; } } diff --git a/sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowRuntimeTest.java b/sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowRuntimeTest.java index 2a3ac846f1..ef99f28b83 100644 --- a/sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowRuntimeTest.java +++ b/sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowRuntimeTest.java @@ -14,17 +14,16 @@ package io.dapr.workflows.runtime; -import com.microsoft.durabletask.DurableTaskGrpcWorkerBuilder; import org.junit.Test; -import java.lang.reflect.Constructor; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; public class WorkflowRuntimeTest { public static class TestWorkflow extends Workflow { @Override - public void run(WorkflowContext ctx) { } + public WorkflowStub create() { + return ctx -> { }; + } } @Test diff --git a/sdk/src/main/java/io/dapr/client/DaprClientBuilder.java b/sdk/src/main/java/io/dapr/client/DaprClientBuilder.java index 202a6c67e5..9b73e5b905 100644 --- a/sdk/src/main/java/io/dapr/client/DaprClientBuilder.java +++ b/sdk/src/main/java/io/dapr/client/DaprClientBuilder.java @@ -16,16 +16,12 @@ 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.dapr.v1.DaprGrpc; import io.grpc.ManagedChannel; -import io.grpc.ManagedChannelBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.Closeable; -import java.net.URI; - /** * A builder for the DaprClient, * Currently only gRPC and HTTP Client will be supported. @@ -163,34 +159,12 @@ private DaprClient buildDaprClient(DaprApiProtocol protocol) { * @throws java.lang.IllegalStateException if either host is missing or if port is missing or a negative number. */ private DaprClient buildDaprClientGrpc() { - final ManagedChannel channel = buildGrpcManagedChanel(); + final ManagedChannel channel = NetworkUtils.buildGrpcManagedChannel(); final GrpcChannelFacade channelFacade = new GrpcChannelFacade(channel); DaprGrpc.DaprStub asyncStub = DaprGrpc.newStub(channel); return new DaprClientGrpc(channelFacade, asyncStub, this.objectSerializer, this.stateSerializer); } - private ManagedChannel buildGrpcManagedChanel() { - String address = Properties.SIDECAR_IP.get(); - int port = Properties.GRPC_PORT.get(); - boolean insecure = true; - String grpcEndpoint = Properties.GRPC_ENDPOINT.get(); - if ((grpcEndpoint != null) && !grpcEndpoint.isEmpty()) { - URI uri = URI.create(grpcEndpoint); - insecure = uri.getScheme().equalsIgnoreCase("http"); - port = uri.getPort() > 0 ? uri.getPort() : (insecure ? 80 : 443); - address = uri.getHost(); - if ((uri.getPath() != null) && !uri.getPath().isEmpty()) { - address += uri.getPath(); - } - } - ManagedChannelBuilder builder = ManagedChannelBuilder.forAddress(address, port) - .userAgent(Version.getSdkVersion()); - if (insecure) { - builder = builder.usePlaintext(); - } - return builder.build(); - } - /** * Creates and instance of DaprClient over HTTP. * diff --git a/sdk/src/main/java/io/dapr/utils/NetworkUtils.java b/sdk/src/main/java/io/dapr/utils/NetworkUtils.java index 840c67ceff..a9ebe8fcd8 100644 --- a/sdk/src/main/java/io/dapr/utils/NetworkUtils.java +++ b/sdk/src/main/java/io/dapr/utils/NetworkUtils.java @@ -13,9 +13,14 @@ package io.dapr.utils; +import io.dapr.config.Properties; +import io.grpc.ManagedChannel; +import io.grpc.ManagedChannelBuilder; + import java.io.IOException; import java.net.InetSocketAddress; import java.net.Socket; +import java.net.URI; /** * Utility methods for network, internal to Dapr SDK. @@ -47,4 +52,30 @@ public static void waitForSocket(String host, int port, int timeoutInMillisecond } }, timeoutInMilliseconds); } + + /** + * Creates a GRPC managed channel. + * @return GRPC managed channel to communicate with the sidecar. + */ + public static ManagedChannel buildGrpcManagedChannel() { + String address = Properties.SIDECAR_IP.get(); + int port = Properties.GRPC_PORT.get(); + boolean insecure = true; + String grpcEndpoint = Properties.GRPC_ENDPOINT.get(); + if ((grpcEndpoint != null) && !grpcEndpoint.isEmpty()) { + URI uri = URI.create(grpcEndpoint); + insecure = uri.getScheme().equalsIgnoreCase("http"); + port = uri.getPort() > 0 ? uri.getPort() : (insecure ? 80 : 443); + address = uri.getHost(); + if ((uri.getPath() != null) && !uri.getPath().isEmpty()) { + address += uri.getPath(); + } + } + ManagedChannelBuilder builder = ManagedChannelBuilder.forAddress(address, port) + .userAgent(Version.getSdkVersion()); + if (insecure) { + builder = builder.usePlaintext(); + } + return builder.build(); + } } From c9f6d1d09fb4fb2eda525250efa976ced807b528 Mon Sep 17 00:00:00 2001 From: Bill DeRusha <444835+bderusha@users.noreply.github.com> Date: Mon, 7 Aug 2023 12:06:53 -0400 Subject: [PATCH 14/18] Fix example unit tests Signed-off-by: Bill DeRusha <444835+bderusha@users.noreply.github.com> --- .../unittesting/DaprWorkflowExampleTest.java | 52 +++++++++++-------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/examples/src/main/java/io/dapr/examples/unittesting/DaprWorkflowExampleTest.java b/examples/src/main/java/io/dapr/examples/unittesting/DaprWorkflowExampleTest.java index 25b1f5168c..407bd58495 100644 --- a/examples/src/main/java/io/dapr/examples/unittesting/DaprWorkflowExampleTest.java +++ b/examples/src/main/java/io/dapr/examples/unittesting/DaprWorkflowExampleTest.java @@ -26,6 +26,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.startsWith; /** * 1. Build and install jars: @@ -36,6 +37,9 @@ * org.junit.platform.console.ConsoleLauncher --select-class=io.dapr.examples.unittesting.DaprWorkflowExampleTest */ public class DaprWorkflowExampleTest { + private static final String timeoutWorkflow = "DemoWorkflowTimeout"; + private static final String noTimeoutWorkflow = "DemoWorkflowNoTimeout"; + private static final String workflowDefaultId = "demo-workflow-123"; private class DemoWorkflow extends Workflow { @@ -45,7 +49,7 @@ public WorkflowStub create() { String name = ctx.getName().block(); String id = ctx.getInstanceId().block(); try { - ctx.waitForExternalEvent("myEvent", Duration.ofSeconds(10)).block(); + ctx.waitForExternalEvent(name, Duration.ofMillis(100)).block(); } catch (TaskCanceledException e) { ctx.getLogger().warn("Timed out"); } @@ -57,16 +61,11 @@ public WorkflowStub create() { @Test public void testWorkflow() { - WorkflowContext mockContext = Mockito.mock(WorkflowContext.class); - String name = "DemoWorkflow"; - String id = "my-workflow-123"; - - Mockito.when(mockContext.getName()).thenReturn(Mono.just(name)); - Mockito.when(mockContext.getInstanceId()).thenReturn(Mono.just(id)); - Mockito.when(mockContext.waitForExternalEvent(anyString(),any(Duration.class))) - .thenReturn(Mono.empty()); + String name = noTimeoutWorkflow; + String id = workflowDefaultId; + WorkflowContext mockContext = createMockContext(name, id); - new DemoWorkflow().create().run(mockContext); + new DemoWorkflow().runAsync(mockContext).block(); String expectedOutput = name + ":" + id; Mockito.verify(mockContext, Mockito.times(1)).complete(expectedOutput); @@ -74,29 +73,38 @@ public void testWorkflow() { @Test public void testWorkflowWaitForEventTimeout() { - WorkflowContext mockContext = Mockito.mock(WorkflowContext.class); + WorkflowContext mockContext = createMockContext(timeoutWorkflow, workflowDefaultId); Logger mockLogger = Mockito.mock(Logger.class); + Mockito.doReturn(mockLogger).when(mockContext).getLogger(); - Mockito.when(mockContext.getLogger()).thenReturn(mockLogger); - Mockito.when(mockContext.waitForExternalEvent(anyString(),any(Duration.class))) - .thenThrow(TaskCanceledException.class); - - new DemoWorkflow().create().run(mockContext); + new DemoWorkflow().runAsync(mockContext).block(); Mockito.verify(mockLogger, Mockito.times(1)).warn("Timed out"); } @Test public void testWorkflowWaitForEventNoTimeout() { - WorkflowContext mockContext = Mockito.mock(WorkflowContext.class); + WorkflowContext mockContext = createMockContext(noTimeoutWorkflow, workflowDefaultId); Logger mockLogger = Mockito.mock(Logger.class); + Mockito.doReturn(mockLogger).when(mockContext).getLogger(); - Mockito.when(mockContext.getLogger()).thenReturn(mockLogger); - Mockito.when(mockContext.waitForExternalEvent(anyString(),any(Duration.class))) - .thenReturn(Mono.empty()); - - new DemoWorkflow().create().run(mockContext); + new DemoWorkflow().runAsync(mockContext).block(); Mockito.verify(mockLogger, Mockito.times(0)).warn(anyString()); } + + private WorkflowContext createMockContext(String name, String id) { + WorkflowContext mockContext = Mockito.mock(WorkflowContext.class); + + Mockito.doReturn(Mono.just(name)).when(mockContext).getName(); + Mockito.doReturn(Mono.just(id)).when(mockContext).getInstanceId(); + Mockito.doReturn(Mono.empty().then()) + .when(mockContext).waitForExternalEvent(startsWith(noTimeoutWorkflow), any(Duration.class)); + Mockito.doThrow(TaskCanceledException.class) + .when(mockContext).waitForExternalEvent(startsWith(timeoutWorkflow), any(Duration.class)); + Mockito.doReturn(Mono.empty().then()) + .when(mockContext).complete(any(Object.class)); + + return mockContext; + } } From 2aba3202a46919a336c9c613f44ebce6b7c6c17f Mon Sep 17 00:00:00 2001 From: Bill DeRusha <444835+bderusha@users.noreply.github.com> Date: Tue, 8 Aug 2023 16:33:49 -0400 Subject: [PATCH 15/18] Implement PR feedback Signed-off-by: Bill DeRusha <444835+bderusha@users.noreply.github.com> --- .../unittesting/DaprWorkflowExampleTest.java | 6 +- .../dapr/examples/workflows/DemoWorkflow.java | 4 +- .../workflows/DemoWorkflowWorker.java | 14 +++-- sdk-workflows/pom.xml | 3 +- .../DaprWorkflowContextImpl.java | 10 ++- .../workflows/{runtime => }/Workflow.java | 2 +- .../{runtime => }/WorkflowContext.java | 2 +- .../workflows/{runtime => }/WorkflowStub.java | 4 +- .../workflows/client/DaprWorkflowClient.java | 21 ++++--- .../runtime/OrchestratorWrapper.java | 15 +++-- .../workflows/runtime/WorkflowRuntime.java | 46 +------------- .../runtime/WorkflowRuntimeBuilder.java | 58 +++++++++++++++++ .../client/DaprWorkflowClientTest.java | 19 ++++-- .../runtime/DaprWorkflowContextImplTest.java | 9 +++ .../runtime/OrchestratorWrapperTest.java | 8 ++- .../runtime/WorkflowRuntimeBuilderTest.java | 24 +++++++ .../runtime/WorkflowRuntimeTest.java | 28 +++++---- .../main/java/io/dapr/utils/NetworkUtils.java | 2 +- .../java/io/dapr/utils/NetworkUtilsTest.java | 62 +++++++++++++++++++ 19 files changed, 240 insertions(+), 97 deletions(-) rename sdk-workflows/src/main/java/io/dapr/workflows/{runtime => }/DaprWorkflowContextImpl.java (95%) rename sdk-workflows/src/main/java/io/dapr/workflows/{runtime => }/Workflow.java (97%) rename sdk-workflows/src/main/java/io/dapr/workflows/{runtime => }/WorkflowContext.java (98%) rename sdk-workflows/src/main/java/io/dapr/workflows/{runtime => }/WorkflowStub.java (90%) create mode 100644 sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowRuntimeBuilder.java create mode 100644 sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowRuntimeBuilderTest.java create mode 100644 sdk/src/test/java/io/dapr/utils/NetworkUtilsTest.java diff --git a/examples/src/main/java/io/dapr/examples/unittesting/DaprWorkflowExampleTest.java b/examples/src/main/java/io/dapr/examples/unittesting/DaprWorkflowExampleTest.java index 407bd58495..1706fc5f78 100644 --- a/examples/src/main/java/io/dapr/examples/unittesting/DaprWorkflowExampleTest.java +++ b/examples/src/main/java/io/dapr/examples/unittesting/DaprWorkflowExampleTest.java @@ -14,9 +14,9 @@ package io.dapr.examples.unittesting; import com.microsoft.durabletask.TaskCanceledException; -import io.dapr.workflows.runtime.Workflow; -import io.dapr.workflows.runtime.WorkflowContext; -import io.dapr.workflows.runtime.WorkflowStub; +import io.dapr.workflows.Workflow; +import io.dapr.workflows.WorkflowContext; +import io.dapr.workflows.WorkflowStub; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.slf4j.Logger; diff --git a/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflow.java b/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflow.java index 2a85b49f55..3bc55639f5 100644 --- a/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflow.java +++ b/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflow.java @@ -14,8 +14,8 @@ package io.dapr.examples.workflows; import com.microsoft.durabletask.TaskCanceledException; -import io.dapr.workflows.runtime.Workflow; -import io.dapr.workflows.runtime.WorkflowStub; +import io.dapr.workflows.Workflow; +import io.dapr.workflows.WorkflowStub; import java.time.Duration; diff --git a/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowWorker.java b/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowWorker.java index 991f0011a4..a51adf3656 100644 --- a/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowWorker.java +++ b/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowWorker.java @@ -14,6 +14,7 @@ package io.dapr.examples.workflows; import io.dapr.workflows.runtime.WorkflowRuntime; +import io.dapr.workflows.runtime.WorkflowRuntimeBuilder; /** * For setup instructions, see the README. @@ -27,10 +28,15 @@ public class DemoWorkflowWorker { * @throws Exception An Exception. */ public static void main(String[] args) throws Exception { - // Register the Workflow with the runtime. - WorkflowRuntime.getInstance().registerWorkflow(DemoWorkflow.class); - System.out.println("Start workflow runtime"); - WorkflowRuntime.getInstance().start().block(); + // Register the Workflow with the builder. + WorkflowRuntimeBuilder builder = new WorkflowRuntimeBuilder().registerWorkflow(DemoWorkflow.class); + + // Build and then start the workflow runtime pulling and executing tasks + try (WorkflowRuntime runtime = builder.build()) { + System.out.println("Start workflow runtime"); + runtime.start().block(); + } + System.exit(0); } } diff --git a/sdk-workflows/pom.xml b/sdk-workflows/pom.xml index f62e8a41ba..2e5171a33f 100644 --- a/sdk-workflows/pom.xml +++ b/sdk-workflows/pom.xml @@ -40,8 +40,7 @@ org.junit.jupiter - junit-jupiter-api - 5.5.2 + junit-jupiter test diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DaprWorkflowContextImpl.java b/sdk-workflows/src/main/java/io/dapr/workflows/DaprWorkflowContextImpl.java similarity index 95% rename from sdk-workflows/src/main/java/io/dapr/workflows/runtime/DaprWorkflowContextImpl.java rename to sdk-workflows/src/main/java/io/dapr/workflows/DaprWorkflowContextImpl.java index 44926a76c2..b127baec53 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/DaprWorkflowContextImpl.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/DaprWorkflowContextImpl.java @@ -11,7 +11,7 @@ limitations under the License. */ -package io.dapr.workflows.runtime; +package io.dapr.workflows; import com.google.protobuf.Empty; import com.microsoft.durabletask.TaskOrchestrationContext; @@ -46,15 +46,13 @@ public DaprWorkflowContextImpl(TaskOrchestrationContext context) throws IllegalA public DaprWorkflowContextImpl(TaskOrchestrationContext context, Logger logger) throws IllegalArgumentException { if (context == null) { throw new IllegalArgumentException("Context cannot be null"); - } else { - this.innerContext = context; } - if (logger == null) { throw new IllegalArgumentException("Logger cannot be null"); - } else { - this.logger = logger; } + + this.innerContext = context; + this.logger = logger; } /** diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/Workflow.java b/sdk-workflows/src/main/java/io/dapr/workflows/Workflow.java similarity index 97% rename from sdk-workflows/src/main/java/io/dapr/workflows/runtime/Workflow.java rename to sdk-workflows/src/main/java/io/dapr/workflows/Workflow.java index 131a319d63..67b8097fe8 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/Workflow.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/Workflow.java @@ -11,7 +11,7 @@ limitations under the License. */ -package io.dapr.workflows.runtime; +package io.dapr.workflows; import com.google.protobuf.Empty; import reactor.core.publisher.Mono; diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowContext.java b/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowContext.java similarity index 98% rename from sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowContext.java rename to sdk-workflows/src/main/java/io/dapr/workflows/WorkflowContext.java index 674b88e917..7ba194ee50 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowContext.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowContext.java @@ -11,7 +11,7 @@ limitations under the License. */ -package io.dapr.workflows.runtime; +package io.dapr.workflows; import org.slf4j.Logger; import reactor.core.publisher.Mono; diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowStub.java b/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowStub.java similarity index 90% rename from sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowStub.java rename to sdk-workflows/src/main/java/io/dapr/workflows/WorkflowStub.java index d10a29ff4f..561a6e1a79 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowStub.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowStub.java @@ -11,7 +11,9 @@ limitations under the License. */ -package io.dapr.workflows.runtime; +package io.dapr.workflows; + +import io.dapr.workflows.WorkflowContext; @FunctionalInterface public interface WorkflowStub { 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 f0e34dbbfd..354cdec5dc 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 @@ -16,7 +16,7 @@ import com.microsoft.durabletask.DurableTaskClient; import com.microsoft.durabletask.DurableTaskGrpcClientBuilder; import io.dapr.utils.NetworkUtils; -import io.dapr.workflows.runtime.Workflow; +import io.dapr.workflows.Workflow; import io.grpc.ManagedChannel; import reactor.core.publisher.Mono; @@ -25,8 +25,8 @@ public class DaprWorkflowClient implements AutoCloseable { - private final DurableTaskClient innerClient; - private final ManagedChannel grpcChannel; + private DurableTaskClient innerClient; + private ManagedChannel grpcChannel; /** * Public constructor for DaprWorkflowClient. This layer constructs the GRPC Channel. @@ -132,11 +132,16 @@ public Mono terminateWorkflow(String workflowInstanceId, @Nullable Object * */ public void close() throws InterruptedException { - if (this.innerClient != null) { - this.innerClient.close(); - } - if (this.grpcChannel != null && !this.grpcChannel.isShutdown()) { - this.grpcChannel.shutdown().awaitTermination(5, TimeUnit.SECONDS); + try { + if (this.innerClient != null) { + this.innerClient.close(); + this.innerClient = null; + } + } finally { + if (this.grpcChannel != null && !this.grpcChannel.isShutdown()) { + this.grpcChannel.shutdown().awaitTermination(5, TimeUnit.SECONDS); + this.grpcChannel = null; + } } } } diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/OrchestratorWrapper.java b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/OrchestratorWrapper.java index 3a0ecd6412..703dcbab7d 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/OrchestratorWrapper.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/OrchestratorWrapper.java @@ -15,6 +15,8 @@ import com.microsoft.durabletask.TaskOrchestration; import com.microsoft.durabletask.TaskOrchestrationFactory; +import io.dapr.workflows.DaprWorkflowContextImpl; +import io.dapr.workflows.Workflow; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; @@ -31,7 +33,9 @@ public OrchestratorWrapper(Class clazz) { try { this.workflowConstructor = clazz.getDeclaredConstructor(); } catch (NoSuchMethodException e) { - throw new RuntimeException(e); + throw new RuntimeException( + String.format("No constructor found for workflow class '%s'.", this.name), e + ); } } @@ -43,12 +47,15 @@ public String getName() { @Override public TaskOrchestration create() { return ctx -> { + T workflow; try { - T workflow = this.workflowConstructor.newInstance(); - workflow.runAsync(new DaprWorkflowContextImpl(ctx)).block(); + workflow = this.workflowConstructor.newInstance(); } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { - throw new RuntimeException(e); + throw new RuntimeException( + String.format("Unable to instantiate instance of workflow class '%s'", this.name), e + ); } + workflow.runAsync(new DaprWorkflowContextImpl(ctx)).block(); }; } diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowRuntime.java b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowRuntime.java index a6c98bb2ba..09999f0f86 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowRuntime.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowRuntime.java @@ -13,10 +13,7 @@ package io.dapr.workflows.runtime; -import com.google.protobuf.Empty; import com.microsoft.durabletask.DurableTaskGrpcWorker; -import com.microsoft.durabletask.DurableTaskGrpcWorkerBuilder; -import io.dapr.utils.NetworkUtils; import reactor.core.publisher.Mono; /** @@ -24,44 +21,10 @@ */ public class WorkflowRuntime implements AutoCloseable { - private static volatile WorkflowRuntime instance; - private DurableTaskGrpcWorkerBuilder builder; private DurableTaskGrpcWorker worker; - private WorkflowRuntime() throws IllegalStateException { - if (instance != null) { - throw new IllegalStateException("WorkflowRuntime should only be constructed once"); - } - - this.builder = new DurableTaskGrpcWorkerBuilder().grpcChannel(NetworkUtils.buildGrpcManagedChannel()); - } - - /** - * Returns an WorkflowRuntime object. - * - * @return An WorkflowRuntime object. - */ - public static WorkflowRuntime getInstance() { - if (instance == null) { - synchronized (WorkflowRuntime.class) { - if (instance == null) { - instance = new WorkflowRuntime(); - } - } - } - return instance; - } - - /** - * Registers a Workflow object. - * - * @param any Workflow type - * @param clazz the class being registered - */ - public void registerWorkflow(Class clazz) { - this.builder = this.builder.addOrchestration( - new OrchestratorWrapper<>(clazz) - ); + public WorkflowRuntime(DurableTaskGrpcWorker worker) { + this.worker = worker; } /** @@ -70,10 +33,7 @@ public void registerWorkflow(Class clazz) { * @return A Mono Plan of type Void. */ public Mono start() { - if (this.worker == null) { - this.worker = this.builder.build(); - } - return Mono.create(it -> { + return Mono.fromRunnable(() -> { this.worker.start(); }).then(); } 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 new file mode 100644 index 0000000000..11fe624baf --- /dev/null +++ b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowRuntimeBuilder.java @@ -0,0 +1,58 @@ +/* + * 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.DurableTaskGrpcWorkerBuilder; +import io.dapr.utils.NetworkUtils; +import io.dapr.workflows.Workflow; + +public class WorkflowRuntimeBuilder { + private static volatile WorkflowRuntime instance; + private DurableTaskGrpcWorkerBuilder builder; + + public WorkflowRuntimeBuilder() { + this.builder = new DurableTaskGrpcWorkerBuilder().grpcChannel(NetworkUtils.buildGrpcManagedChannel()); + } + + /** + * Returns a WorkflowRuntime object. + * + * @return A WorkflowRuntime object. + */ + public WorkflowRuntime build() { + if (instance == null) { + synchronized (WorkflowRuntime.class) { + if (instance == null) { + instance = new WorkflowRuntime(this.builder.build()); + } + } + } + return instance; + } + + /** + * Registers a Workflow object. + * + * @param any Workflow type + * @param clazz the class being registered + * @return the WorkflowRuntimeBuilder + */ + public WorkflowRuntimeBuilder registerWorkflow(Class clazz) { + this.builder = this.builder.addOrchestration( + new OrchestratorWrapper<>(clazz) + ); + + return this; + } +} 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 10fc731119..befa49e200 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 @@ -14,8 +14,8 @@ package io.dapr.workflows.client; import com.microsoft.durabletask.DurableTaskClient; -import io.dapr.workflows.runtime.Workflow; -import io.dapr.workflows.runtime.WorkflowStub; +import io.dapr.workflows.Workflow; +import io.dapr.workflows.WorkflowStub; import io.grpc.ManagedChannel; import org.junit.Before; import org.junit.BeforeClass; @@ -24,6 +24,7 @@ import java.lang.reflect.Constructor; import java.util.Arrays; +import static org.junit.Assert.assertThrows; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.mockito.Mockito.*; @@ -44,10 +45,7 @@ public WorkflowStub create() { public static void beforeAll() { constructor = Constructor.class.cast(Arrays.stream(DaprWorkflowClient.class.getDeclaredConstructors()) - .filter(c -> c.getParameters().length == 2).map(c -> { - c.setAccessible(true); - return c; - }).findFirst().get()); + .filter(c -> c.getParameters().length == 2).peek(c -> c.setAccessible(true)).findFirst().get()); } @Before @@ -110,4 +108,13 @@ public void close() throws InterruptedException { verify(mockInnerClient, times(1)).close(); verify(mockGrpcChannel, times(1)).shutdown(); } + + @Test + public void closeWithInnerClientRuntimeException() throws InterruptedException { + doThrow(RuntimeException.class).when(mockInnerClient).close(); + + assertThrows(RuntimeException.class, () -> { client.close(); }); + verify(mockInnerClient, times(1)).close(); + verify(mockGrpcChannel, times(1)).shutdown(); + } } diff --git a/sdk-workflows/src/test/java/io/dapr/workflows/runtime/DaprWorkflowContextImplTest.java b/sdk-workflows/src/test/java/io/dapr/workflows/runtime/DaprWorkflowContextImplTest.java index 23967e796b..02853c0ba7 100644 --- a/sdk-workflows/src/test/java/io/dapr/workflows/runtime/DaprWorkflowContextImplTest.java +++ b/sdk-workflows/src/test/java/io/dapr/workflows/runtime/DaprWorkflowContextImplTest.java @@ -15,12 +15,14 @@ import com.microsoft.durabletask.Task; import com.microsoft.durabletask.TaskOrchestrationContext; +import io.dapr.workflows.DaprWorkflowContextImpl; import org.junit.Before; import org.junit.Test; import org.slf4j.Logger; import java.time.Duration; +import static org.junit.Assert.assertThrows; import static org.mockito.Mockito.*; public class DaprWorkflowContextImplTest { @@ -33,6 +35,13 @@ public void setUp() { context = new DaprWorkflowContextImpl(mockInnerContext); } + @Test + public void nullConstructorTest() { + assertThrows(IllegalArgumentException.class, () -> { new DaprWorkflowContextImpl(mockInnerContext, null); }); + assertThrows(IllegalArgumentException.class, () -> { new DaprWorkflowContextImpl(null, mock(Logger.class)); }); + assertThrows(IllegalArgumentException.class, () -> { new DaprWorkflowContextImpl(null, null); }); + } + @Test public void getNameTest() { context.getName().block(); diff --git a/sdk-workflows/src/test/java/io/dapr/workflows/runtime/OrchestratorWrapperTest.java b/sdk-workflows/src/test/java/io/dapr/workflows/runtime/OrchestratorWrapperTest.java index 463e335e0b..e0e7116ec0 100644 --- a/sdk-workflows/src/test/java/io/dapr/workflows/runtime/OrchestratorWrapperTest.java +++ b/sdk-workflows/src/test/java/io/dapr/workflows/runtime/OrchestratorWrapperTest.java @@ -15,13 +15,15 @@ import com.microsoft.durabletask.TaskOrchestrationContext; +import io.dapr.workflows.Workflow; +import io.dapr.workflows.WorkflowStub; import org.junit.Assert; import org.junit.Test; import static org.mockito.Mockito.*; public class OrchestratorWrapperTest { - public static class TestWorkflow extends Workflow{ + public static class TestWorkflow extends Workflow { @Override public WorkflowStub create() { return ctx -> { @@ -32,7 +34,7 @@ public WorkflowStub create() { @Test public void getName() throws NoSuchMethodException { - OrchestratorWrapper wrapper = new OrchestratorWrapper(TestWorkflow.class); + OrchestratorWrapper wrapper = new OrchestratorWrapper<>(TestWorkflow.class); Assert.assertEquals( "io.dapr.workflows.runtime.OrchestratorWrapperTest.TestWorkflow", wrapper.getName() @@ -42,7 +44,7 @@ public void getName() throws NoSuchMethodException { @Test public void createWithClass() throws NoSuchMethodException { TaskOrchestrationContext mockContext = mock(TaskOrchestrationContext.class); - OrchestratorWrapper wrapper = new OrchestratorWrapper(TestWorkflow.class); + OrchestratorWrapper wrapper = new OrchestratorWrapper<>(TestWorkflow.class); 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 new file mode 100644 index 0000000000..905b89e9da --- /dev/null +++ b/sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowRuntimeBuilderTest.java @@ -0,0 +1,24 @@ +package io.dapr.workflows.runtime; + + +import io.dapr.workflows.Workflow; +import io.dapr.workflows.WorkflowStub; +import org.junit.Test; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +public class WorkflowRuntimeBuilderTest { + public static class TestWorkflow extends Workflow { + @Override + public WorkflowStub create() { + return ctx -> { }; + } + } + + @Test + public void registerValidWorkflowClass() { + assertDoesNotThrow(() -> new WorkflowRuntimeBuilder().registerWorkflow(TestWorkflow.class)); + } + + +} diff --git a/sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowRuntimeTest.java b/sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowRuntimeTest.java index ef99f28b83..5fe84de1c4 100644 --- a/sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowRuntimeTest.java +++ b/sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowRuntimeTest.java @@ -14,8 +14,14 @@ package io.dapr.workflows.runtime; +import com.microsoft.durabletask.DurableTaskGrpcWorker; +import com.microsoft.durabletask.DurableTaskGrpcWorkerBuilder; +import io.dapr.workflows.Workflow; +import io.dapr.workflows.WorkflowStub; import org.junit.Test; +import java.time.Duration; + import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; public class WorkflowRuntimeTest { @@ -26,23 +32,21 @@ public WorkflowStub create() { } } - @Test - public void registerValidWorkflowClass() { - assertDoesNotThrow(() -> WorkflowRuntime.getInstance().registerWorkflow(TestWorkflow.class)); - } - @Test public void startTest() { - assertDoesNotThrow(() -> { - WorkflowRuntime.getInstance().start(); - WorkflowRuntime.getInstance().close(); - }); + DurableTaskGrpcWorker worker = new DurableTaskGrpcWorkerBuilder().build(); + try (WorkflowRuntime runtime = new WorkflowRuntime(worker)) { + assertDoesNotThrow(() -> { + runtime.start().block(Duration.ofSeconds(1)); + }); + } } @Test public void closeWithoutStarting() { - assertDoesNotThrow(() -> { - WorkflowRuntime.getInstance().close(); - }); + DurableTaskGrpcWorker worker = new DurableTaskGrpcWorkerBuilder().build(); + try (WorkflowRuntime runtime = new WorkflowRuntime(worker)) { + assertDoesNotThrow(runtime::close); + } } } diff --git a/sdk/src/main/java/io/dapr/utils/NetworkUtils.java b/sdk/src/main/java/io/dapr/utils/NetworkUtils.java index a9ebe8fcd8..8f755f3131 100644 --- a/sdk/src/main/java/io/dapr/utils/NetworkUtils.java +++ b/sdk/src/main/java/io/dapr/utils/NetworkUtils.java @@ -71,7 +71,7 @@ public static ManagedChannel buildGrpcManagedChannel() { address += uri.getPath(); } } - ManagedChannelBuilder builder = ManagedChannelBuilder.forAddress(address, port) + ManagedChannelBuilder builder = ManagedChannelBuilder.forAddress(address, port) .userAgent(Version.getSdkVersion()); if (insecure) { builder = builder.usePlaintext(); diff --git a/sdk/src/test/java/io/dapr/utils/NetworkUtilsTest.java b/sdk/src/test/java/io/dapr/utils/NetworkUtilsTest.java new file mode 100644 index 0000000000..0c1417ceab --- /dev/null +++ b/sdk/src/test/java/io/dapr/utils/NetworkUtilsTest.java @@ -0,0 +1,62 @@ +package io.dapr.utils; + +import io.dapr.config.Properties; +import io.grpc.ManagedChannel; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class NetworkUtilsTest { + private final int defaultGrpcPort = 4000; + private final String defaultSidecarIP = "127.0.0.1"; + @Before + public void setUp() { + System.setProperty(Properties.GRPC_PORT.getName(), Integer.toString(defaultGrpcPort)); + System.setProperty(Properties.SIDECAR_IP.getName(), defaultSidecarIP); + System.setProperty(Properties.GRPC_ENDPOINT.getName(), ""); + } + + @Test + public void testBuildGrpcManagedChannel() { + ManagedChannel channel = NetworkUtils.buildGrpcManagedChannel(); + + String expectedAuthority = String.format("%s:%s", defaultSidecarIP, defaultGrpcPort); + Assert.assertEquals(expectedAuthority, channel.authority()); + } + + @Test + public void testBuildGrpcManagedChannel_httpEndpointNoPort() { + System.setProperty(Properties.GRPC_ENDPOINT.getName(), "http://example.com"); + ManagedChannel channel = NetworkUtils.buildGrpcManagedChannel(); + + String expectedAuthority = "example.com:80"; + Assert.assertEquals(expectedAuthority, channel.authority()); + } + + @Test + public void testBuildGrpcManagedChannel_httpEndpointWithPort() { + System.setProperty(Properties.GRPC_ENDPOINT.getName(), "http://example.com:3000"); + ManagedChannel channel = NetworkUtils.buildGrpcManagedChannel(); + + String expectedAuthority = "example.com:3000"; + Assert.assertEquals(expectedAuthority, channel.authority()); + } + + @Test + public void testBuildGrpcManagedChannel_httpsEndpointNoPort() { + System.setProperty(Properties.GRPC_ENDPOINT.getName(), "https://example.com"); + ManagedChannel channel = NetworkUtils.buildGrpcManagedChannel(); + + String expectedAuthority = "example.com:443"; + Assert.assertEquals(expectedAuthority, channel.authority()); + } + + @Test + public void testBuildGrpcManagedChannel_httpsEndpointWithPort() { + System.setProperty(Properties.GRPC_ENDPOINT.getName(), "https://example.com:3000"); + ManagedChannel channel = NetworkUtils.buildGrpcManagedChannel(); + + String expectedAuthority = "example.com:3000"; + Assert.assertEquals(expectedAuthority, channel.authority()); + } +} From 6af50b506e08d59f3ccf51a0def1878e42b0dbbc Mon Sep 17 00:00:00 2001 From: Bill DeRusha <444835+bderusha@users.noreply.github.com> Date: Wed, 9 Aug 2023 17:17:04 -0400 Subject: [PATCH 16/18] PR Feedback: Revert Mono usage + pom.xml changes Signed-off-by: Bill DeRusha <444835+bderusha@users.noreply.github.com> --- .../unittesting/DaprWorkflowExampleTest.java | 6 ++-- .../dapr/examples/workflows/DemoWorkflow.java | 6 ++-- .../workflows/DemoWorkflowClient.java | 6 ++-- pom.xml | 1 + scripts/update_sdk_version.sh | 4 ++- sdk-workflows/pom.xml | 2 +- .../workflows/DaprWorkflowContextImpl.java | 21 +++++------ .../main/java/io/dapr/workflows/Workflow.java | 11 ++---- .../io/dapr/workflows/WorkflowContext.java | 11 +++--- .../workflows/client/DaprWorkflowClient.java | 36 ++++++------------- .../runtime/OrchestratorWrapper.java | 2 +- .../client/DaprWorkflowClientTest.java | 8 ++--- .../runtime/DaprWorkflowContextImplTest.java | 6 ++-- .../runtime/OrchestratorWrapperTest.java | 9 +++-- 14 files changed, 52 insertions(+), 77 deletions(-) diff --git a/examples/src/main/java/io/dapr/examples/unittesting/DaprWorkflowExampleTest.java b/examples/src/main/java/io/dapr/examples/unittesting/DaprWorkflowExampleTest.java index 1706fc5f78..7614b4b32c 100644 --- a/examples/src/main/java/io/dapr/examples/unittesting/DaprWorkflowExampleTest.java +++ b/examples/src/main/java/io/dapr/examples/unittesting/DaprWorkflowExampleTest.java @@ -46,15 +46,15 @@ private class DemoWorkflow extends Workflow { @Override public WorkflowStub create() { return ctx -> { - String name = ctx.getName().block(); - String id = ctx.getInstanceId().block(); + String name = ctx.getName(); + String id = ctx.getInstanceId(); try { ctx.waitForExternalEvent(name, Duration.ofMillis(100)).block(); } catch (TaskCanceledException e) { ctx.getLogger().warn("Timed out"); } String output = name + ":" + id; - ctx.complete(output).block(); + ctx.complete(output); }; } } diff --git a/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflow.java b/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflow.java index 3bc55639f5..6059e50f0b 100644 --- a/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflow.java +++ b/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflow.java @@ -27,8 +27,8 @@ public class DemoWorkflow extends Workflow { @Override public WorkflowStub create() { return ctx -> { - ctx.getLogger().info("Starting Workflow: " + ctx.getName().block()); - ctx.getLogger().info("Instance ID: " + ctx.getInstanceId().block()); + ctx.getLogger().info("Starting Workflow: " + ctx.getName()); + ctx.getLogger().info("Instance ID: " + ctx.getInstanceId()); ctx.getLogger().info("Waiting for event: 'myEvent'..."); try { ctx.waitForExternalEvent("myEvent", Duration.ofSeconds(10)).block(); @@ -37,7 +37,7 @@ public WorkflowStub create() { ctx.getLogger().warn("Timed out"); ctx.getLogger().warn(e.getMessage()); } - ctx.complete("finished").block(); + ctx.complete("finished"); }; } } diff --git a/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowClient.java b/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowClient.java index eebac25cda..cf439c969f 100644 --- a/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowClient.java +++ b/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowClient.java @@ -32,7 +32,7 @@ public static void main(String[] args) throws InterruptedException { try (client) { System.out.println("*****"); - String instanceId = client.scheduleNewWorkflow(DemoWorkflow.class).block(); + String instanceId = client.scheduleNewWorkflow(DemoWorkflow.class); System.out.printf("Started new workflow instance with random ID: %s%n", instanceId); System.out.println("Sleep and allow this workflow instance to timeout..."); @@ -40,12 +40,12 @@ public static void main(String[] args) throws InterruptedException { System.out.println("*****"); String instanceToTerminateId = "terminateMe"; - client.scheduleNewWorkflow(DemoWorkflow.class, null, instanceToTerminateId).block(); + client.scheduleNewWorkflow(DemoWorkflow.class, null, instanceToTerminateId); System.out.printf("Started new workflow instance with specified ID: %s%n", instanceToTerminateId); TimeUnit.SECONDS.sleep(5); System.out.println("Terminate this workflow instance manually before the timeout is reached"); - client.terminateWorkflow(instanceToTerminateId, null).block(); + client.terminateWorkflow(instanceToTerminateId, null); System.out.println("*****"); } diff --git a/pom.xml b/pom.xml index fcd84c7d7d..947f63a5e2 100644 --- a/pom.xml +++ b/pom.xml @@ -17,6 +17,7 @@ 1.42.1 3.17.3 https://raw.githubusercontent.com/dapr/dapr/v1.11.0-rc.5/dapr/proto + 0.10.0-SNAPSHOT 1.6.2 3.1.1 1.8 diff --git a/scripts/update_sdk_version.sh b/scripts/update_sdk_version.sh index ac8663e977..64bc9b7397 100755 --- a/scripts/update_sdk_version.sh +++ b/scripts/update_sdk_version.sh @@ -3,9 +3,11 @@ set -uex DAPR_JAVA_SDK_VERSION=$1 +# replace the major version of DAPR_JAVA_SDK_VERSION with 0 while in alpha +DAPR_WORKFLOW_SDK_VERSION=$(echo $DAPR_JAVA_SDK_VERSION | sed -E "s/[0-9]+\.(.*?)/0.\1/") mvn versions:set -DnewVersion=$DAPR_JAVA_SDK_VERSION -mvn versions:commit +mvn versions:set-property -Dproperty=dapr.sdk-workflows.version -DnewVersion=$DAPR_WORKFLOW_SDK_VERSION if [[ "$OSTYPE" == "darwin"* ]]; then sed -i bak "s/.*<\/dapr.sdk.version>/${DAPR_JAVA_SDK_VERSION}<\/dapr.sdk.version>/g" sdk-tests/pom.xml diff --git a/sdk-workflows/pom.xml b/sdk-workflows/pom.xml index 2e5171a33f..74f2e15fe5 100644 --- a/sdk-workflows/pom.xml +++ b/sdk-workflows/pom.xml @@ -12,7 +12,7 @@ dapr-sdk-workflows jar - 0.1.0-SNAPSHOT + ${dapr.sdk-workflows.version} dapr-sdk-workflows SDK for Workflows on Dapr diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/DaprWorkflowContextImpl.java b/sdk-workflows/src/main/java/io/dapr/workflows/DaprWorkflowContextImpl.java index b127baec53..124f0d7c61 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/DaprWorkflowContextImpl.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/DaprWorkflowContextImpl.java @@ -13,7 +13,6 @@ package io.dapr.workflows; -import com.google.protobuf.Empty; import com.microsoft.durabletask.TaskOrchestrationContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -68,34 +67,30 @@ public Logger getLogger() { /** * {@inheritDoc} */ - public Mono getName() { - return Mono.create(it -> it.success(this.innerContext.getName())); + public String getName() { + return this.innerContext.getName(); } /** * {@inheritDoc} */ - public Mono getInstanceId() { - return Mono.create(it -> it.success(this.innerContext.getInstanceId())); + public String getInstanceId() { + return this.innerContext.getInstanceId(); } /** * {@inheritDoc} */ - public Mono complete(Object output) { - return Mono.create(it -> { - this.innerContext.complete(output); - it.success(); - }).then(); + public void complete(Object output) { + this.innerContext.complete(output); } /** * {@inheritDoc} */ public Mono waitForExternalEvent(String eventName, Duration timeout) { - return Mono.create(it -> { + return Mono.fromRunnable(() -> { this.innerContext.waitForExternalEvent(eventName, timeout).await(); - it.success(); - }).then(); + }); } } 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 67b8097fe8..66b5c02d73 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/Workflow.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/Workflow.java @@ -13,9 +13,6 @@ package io.dapr.workflows; -import com.google.protobuf.Empty; -import reactor.core.publisher.Mono; - /** * Common interface for workflow implementations. */ @@ -35,12 +32,8 @@ public Workflow(){ * * @param ctx provides access to methods for scheduling durable tasks and getting information about the current * workflow instance. - * @return A Mono Plan of type Void. */ - public Mono runAsync(WorkflowContext ctx) { - return Mono.create(it -> { - this.create().run(ctx); - it.success(); - }).then(); + public void run(WorkflowContext ctx) { + this.create().run(ctx); } } 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 7ba194ee50..9f482f360d 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowContext.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowContext.java @@ -37,24 +37,23 @@ public interface WorkflowContext { /** * Gets the name of the current workflow. * - * @return Asynchronous result with the name of the current workflow + * @return the name of the current workflow */ - Mono getName(); + String getName(); /** * Gets the instance ID of the current workflow. * - * @return Asynchronous result with the instance ID of the current workflow + * @return the instance ID of the current workflow */ - Mono getInstanceId(); + String getInstanceId(); /** * Completes the current workflow. * * @param output the serializable output of the completed Workflow. - * @return A Mono Plan of type Void. */ - Mono complete(Object output); + void complete(Object output); /** * Waits for an event to be raised with name and returns the event data. 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 354cdec5dc..9f4ab03ad4 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 @@ -18,7 +18,6 @@ import io.dapr.utils.NetworkUtils; import io.dapr.workflows.Workflow; import io.grpc.ManagedChannel; -import reactor.core.publisher.Mono; import javax.annotation.Nullable; import java.util.concurrent.TimeUnit; @@ -73,13 +72,10 @@ private static DurableTaskClient createDurableTaskClient(ManagedChannel grpcChan * * @param any Workflow type * @param clazz Class extending Workflow to start an instance of. - * @return A Mono Plan of type String with the randomly-generated instance ID for new Workflow instance. + * @return A String with the randomly-generated instance ID for new Workflow instance. */ - public Mono scheduleNewWorkflow(Class clazz) { - return Mono.create(it -> { - String response = this.innerClient.scheduleNewOrchestrationInstance(clazz.getCanonicalName()); - it.success(response); - }); + public String scheduleNewWorkflow(Class clazz) { + return this.innerClient.scheduleNewOrchestrationInstance(clazz.getCanonicalName()); } /** @@ -88,13 +84,10 @@ public Mono scheduleNewWorkflow(Class clazz) { * @param any Workflow type * @param clazz Class extending Workflow to start an instance of. * @param input the input to pass to the scheduled orchestration instance. Must be serializable. - * @return A Mono Plan of type String with the randomly-generated instance ID for new Workflow instance. + * @return A String with the randomly-generated instance ID for new Workflow instance. */ - public Mono scheduleNewWorkflow(Class clazz, Object input) { - return Mono.create(it -> { - String response = this.innerClient.scheduleNewOrchestrationInstance(clazz.getCanonicalName(), input); - it.success(response); - }); + public String scheduleNewWorkflow(Class clazz, Object input) { + return this.innerClient.scheduleNewOrchestrationInstance(clazz.getCanonicalName(), input); } /** @@ -104,13 +97,10 @@ public Mono scheduleNewWorkflow(Class clazz, Obj * @param clazz Class extending Workflow to start an instance of. * @param input the input to pass to the scheduled orchestration instance. Must be serializable. * @param instanceId the unique ID of the orchestration instance to schedule - * @return A Mono Plan of type String with the instanceId parameter value. + * @return A String with the instanceId parameter value. */ - public Mono scheduleNewWorkflow(Class clazz, Object input, String instanceId) { - return Mono.create(it -> { - String response = this.innerClient.scheduleNewOrchestrationInstance(clazz.getCanonicalName(), input, instanceId); - it.success(response); - }); + public String scheduleNewWorkflow(Class clazz, Object input, String instanceId) { + return this.innerClient.scheduleNewOrchestrationInstance(clazz.getCanonicalName(), input, instanceId); } /** @@ -118,13 +108,9 @@ public Mono scheduleNewWorkflow(Class clazz, Obj * * @param workflowInstanceId Workflow instance id to terminate. * @param output the optional output to set for the terminated orchestration instance. - * @return A Mono Plan of type Void. */ - public Mono terminateWorkflow(String workflowInstanceId, @Nullable Object output) { - return Mono.create(it -> { - this.innerClient.terminate(workflowInstanceId, output); - it.success(); - }).then(); + public void terminateWorkflow(String workflowInstanceId, @Nullable Object output) { + this.innerClient.terminate(workflowInstanceId, output); } /** diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/OrchestratorWrapper.java b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/OrchestratorWrapper.java index 703dcbab7d..f28eed0de7 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/OrchestratorWrapper.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/OrchestratorWrapper.java @@ -55,7 +55,7 @@ public TaskOrchestration create() { String.format("Unable to instantiate instance of workflow class '%s'", this.name), e ); } - workflow.runAsync(new DaprWorkflowContextImpl(ctx)).block(); + workflow.run(new DaprWorkflowContextImpl(ctx)); }; } 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 befa49e200..a3f8261230 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 @@ -66,7 +66,7 @@ public void EmptyConstructor() { public void scheduleNewWorkflowWithArgName() { String expectedName = TestWorkflow.class.getCanonicalName(); - client.scheduleNewWorkflow(TestWorkflow.class).block(); + client.scheduleNewWorkflow(TestWorkflow.class); verify(mockInnerClient, times(1)).scheduleNewOrchestrationInstance(expectedName); } @@ -76,7 +76,7 @@ public void scheduleNewWorkflowWithArgsNameInput() { String expectedName = TestWorkflow.class.getCanonicalName(); Object expectedInput = new Object(); - client.scheduleNewWorkflow(TestWorkflow.class, expectedInput).block(); + client.scheduleNewWorkflow(TestWorkflow.class, expectedInput); verify(mockInnerClient, times(1)) .scheduleNewOrchestrationInstance(expectedName, expectedInput); @@ -88,7 +88,7 @@ public void scheduleNewWorkflowWithArgsNameInputInstance() { Object expectedInput = new Object(); String expectedInstanceId = "myTestInstance123"; - client.scheduleNewWorkflow(TestWorkflow.class, expectedInput, expectedInstanceId).block(); + client.scheduleNewWorkflow(TestWorkflow.class, expectedInput, expectedInstanceId); verify(mockInnerClient, times(1)) .scheduleNewOrchestrationInstance(expectedName, expectedInput, expectedInstanceId); @@ -98,7 +98,7 @@ public void scheduleNewWorkflowWithArgsNameInputInstance() { public void terminateWorkflow() { String expectedArgument = "TestWorkflowInstanceId"; - client.terminateWorkflow(expectedArgument, null).block(); + client.terminateWorkflow(expectedArgument, null); verify(mockInnerClient, times(1)).terminate(expectedArgument, null); } diff --git a/sdk-workflows/src/test/java/io/dapr/workflows/runtime/DaprWorkflowContextImplTest.java b/sdk-workflows/src/test/java/io/dapr/workflows/runtime/DaprWorkflowContextImplTest.java index 02853c0ba7..76030c1f30 100644 --- a/sdk-workflows/src/test/java/io/dapr/workflows/runtime/DaprWorkflowContextImplTest.java +++ b/sdk-workflows/src/test/java/io/dapr/workflows/runtime/DaprWorkflowContextImplTest.java @@ -44,13 +44,13 @@ public void nullConstructorTest() { @Test public void getNameTest() { - context.getName().block(); + context.getName(); verify(mockInnerContext, times(1)).getName(); } @Test public void getInstanceIdTest() { - context.getInstanceId().block(); + context.getInstanceId(); verify(mockInnerContext, times(1)).getInstanceId(); } @@ -73,7 +73,7 @@ public void DaprWorkflowContextWithEmptyInnerContext() { @Test public void completeTest() { - context.complete(null).block(); + context.complete(null); verify(mockInnerContext, times(1)).complete(null); } diff --git a/sdk-workflows/src/test/java/io/dapr/workflows/runtime/OrchestratorWrapperTest.java b/sdk-workflows/src/test/java/io/dapr/workflows/runtime/OrchestratorWrapperTest.java index e0e7116ec0..eff6ccd987 100644 --- a/sdk-workflows/src/test/java/io/dapr/workflows/runtime/OrchestratorWrapperTest.java +++ b/sdk-workflows/src/test/java/io/dapr/workflows/runtime/OrchestratorWrapperTest.java @@ -16,6 +16,7 @@ import com.microsoft.durabletask.TaskOrchestrationContext; import io.dapr.workflows.Workflow; +import io.dapr.workflows.WorkflowContext; import io.dapr.workflows.WorkflowStub; import org.junit.Assert; import org.junit.Test; @@ -26,14 +27,12 @@ public class OrchestratorWrapperTest { public static class TestWorkflow extends Workflow { @Override public WorkflowStub create() { - return ctx -> { - ctx.getInstanceId().block(); - }; + return WorkflowContext::getInstanceId; } } @Test - public void getName() throws NoSuchMethodException { + public void getName() { OrchestratorWrapper wrapper = new OrchestratorWrapper<>(TestWorkflow.class); Assert.assertEquals( "io.dapr.workflows.runtime.OrchestratorWrapperTest.TestWorkflow", @@ -42,7 +41,7 @@ public void getName() throws NoSuchMethodException { } @Test - public void createWithClass() throws NoSuchMethodException { + public void createWithClass() { TaskOrchestrationContext mockContext = mock(TaskOrchestrationContext.class); OrchestratorWrapper wrapper = new OrchestratorWrapper<>(TestWorkflow.class); when( mockContext.getInstanceId() ).thenReturn("uuid"); From 4a53c4e51b72046f6a75ad37781287b8cccb5032 Mon Sep 17 00:00:00 2001 From: Bill DeRusha <444835+bderusha@users.noreply.github.com> Date: Thu, 10 Aug 2023 09:30:46 -0400 Subject: [PATCH 17/18] pom.xml version fixes Signed-off-by: Bill DeRusha <444835+bderusha@users.noreply.github.com> --- examples/pom.xml | 2 +- .../unittesting/DaprWorkflowExampleTest.java | 12 +++++------- scripts/update_sdk_version.sh | 10 +++------- sdk-workflows/pom.xml | 2 +- 4 files changed, 10 insertions(+), 16 deletions(-) diff --git a/examples/pom.xml b/examples/pom.xml index ac8e093c51..216b9845f7 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -121,7 +121,7 @@ io.dapr dapr-sdk-workflows - 0.1.0-SNAPSHOT + ${dapr.sdk-workflows.version} io.dapr diff --git a/examples/src/main/java/io/dapr/examples/unittesting/DaprWorkflowExampleTest.java b/examples/src/main/java/io/dapr/examples/unittesting/DaprWorkflowExampleTest.java index 7614b4b32c..3f269a6d93 100644 --- a/examples/src/main/java/io/dapr/examples/unittesting/DaprWorkflowExampleTest.java +++ b/examples/src/main/java/io/dapr/examples/unittesting/DaprWorkflowExampleTest.java @@ -65,7 +65,7 @@ public void testWorkflow() { String id = workflowDefaultId; WorkflowContext mockContext = createMockContext(name, id); - new DemoWorkflow().runAsync(mockContext).block(); + new DemoWorkflow().run(mockContext); String expectedOutput = name + ":" + id; Mockito.verify(mockContext, Mockito.times(1)).complete(expectedOutput); @@ -77,7 +77,7 @@ public void testWorkflowWaitForEventTimeout() { Logger mockLogger = Mockito.mock(Logger.class); Mockito.doReturn(mockLogger).when(mockContext).getLogger(); - new DemoWorkflow().runAsync(mockContext).block(); + new DemoWorkflow().run(mockContext); Mockito.verify(mockLogger, Mockito.times(1)).warn("Timed out"); } @@ -88,7 +88,7 @@ public void testWorkflowWaitForEventNoTimeout() { Logger mockLogger = Mockito.mock(Logger.class); Mockito.doReturn(mockLogger).when(mockContext).getLogger(); - new DemoWorkflow().runAsync(mockContext).block(); + new DemoWorkflow().run(mockContext); Mockito.verify(mockLogger, Mockito.times(0)).warn(anyString()); } @@ -96,14 +96,12 @@ public void testWorkflowWaitForEventNoTimeout() { private WorkflowContext createMockContext(String name, String id) { WorkflowContext mockContext = Mockito.mock(WorkflowContext.class); - Mockito.doReturn(Mono.just(name)).when(mockContext).getName(); - Mockito.doReturn(Mono.just(id)).when(mockContext).getInstanceId(); + Mockito.doReturn(name).when(mockContext).getName(); + Mockito.doReturn(id).when(mockContext).getInstanceId(); Mockito.doReturn(Mono.empty().then()) .when(mockContext).waitForExternalEvent(startsWith(noTimeoutWorkflow), any(Duration.class)); Mockito.doThrow(TaskCanceledException.class) .when(mockContext).waitForExternalEvent(startsWith(timeoutWorkflow), any(Duration.class)); - Mockito.doReturn(Mono.empty().then()) - .when(mockContext).complete(any(Object.class)); return mockContext; } diff --git a/scripts/update_sdk_version.sh b/scripts/update_sdk_version.sh index 64bc9b7397..fce2a5486d 100755 --- a/scripts/update_sdk_version.sh +++ b/scripts/update_sdk_version.sh @@ -7,11 +7,7 @@ DAPR_JAVA_SDK_VERSION=$1 DAPR_WORKFLOW_SDK_VERSION=$(echo $DAPR_JAVA_SDK_VERSION | sed -E "s/[0-9]+\.(.*?)/0.\1/") mvn versions:set -DnewVersion=$DAPR_JAVA_SDK_VERSION -mvn versions:set-property -Dproperty=dapr.sdk-workflows.version -DnewVersion=$DAPR_WORKFLOW_SDK_VERSION +mvn versions:set-property -Dproperty=dapr.sdk.version -DnewVersion=$DAPR_JAVA_SDK_VERSION -f sdk-tests/pom.xml -if [[ "$OSTYPE" == "darwin"* ]]; then - sed -i bak "s/.*<\/dapr.sdk.version>/${DAPR_JAVA_SDK_VERSION}<\/dapr.sdk.version>/g" sdk-tests/pom.xml - rm sdk-tests/pom.xmlbak -else - sed -i "s/.*<\/dapr.sdk.version>/${DAPR_JAVA_SDK_VERSION}<\/dapr.sdk.version>/g" sdk-tests/pom.xml -fi +mvn versions:set -DnewVersion=$DAPR_WORKFLOW_SDK_VERSION -f sdk-workflows/pom.xml +mvn versions:set-property -Dproperty=dapr.sdk-workflows.version -DnewVersion=$DAPR_WORKFLOW_SDK_VERSION diff --git a/sdk-workflows/pom.xml b/sdk-workflows/pom.xml index 74f2e15fe5..51051e35d8 100644 --- a/sdk-workflows/pom.xml +++ b/sdk-workflows/pom.xml @@ -12,7 +12,7 @@ dapr-sdk-workflows jar - ${dapr.sdk-workflows.version} + 0.10.0-SNAPSHOT dapr-sdk-workflows SDK for Workflows on Dapr From 904e41ee7afa9b75010cfb03d5e026783fab1081 Mon Sep 17 00:00:00 2001 From: Bill DeRusha <444835+bderusha@users.noreply.github.com> Date: Thu, 10 Aug 2023 10:35:53 -0400 Subject: [PATCH 18/18] Remove Mono entirely from workflows Signed-off-by: Bill DeRusha <444835+bderusha@users.noreply.github.com> --- .../unittesting/DaprWorkflowExampleTest.java | 13 +++++++------ .../dapr/examples/workflows/DemoWorkflow.java | 2 +- .../workflows/DemoWorkflowWorker.java | 2 +- .../workflows/DaprWorkflowContextImpl.java | 8 +++----- .../io/dapr/workflows/WorkflowContext.java | 6 +++--- .../workflows/runtime/WorkflowRuntime.java | 19 ++++++++++++++----- .../DaprWorkflowContextImplTest.java | 4 ++-- .../runtime/WorkflowRuntimeTest.java | 4 +--- 8 files changed, 32 insertions(+), 26 deletions(-) rename sdk-workflows/src/test/java/io/dapr/workflows/{runtime => }/DaprWorkflowContextImplTest.java (98%) diff --git a/examples/src/main/java/io/dapr/examples/unittesting/DaprWorkflowExampleTest.java b/examples/src/main/java/io/dapr/examples/unittesting/DaprWorkflowExampleTest.java index 3f269a6d93..f85f9e013b 100644 --- a/examples/src/main/java/io/dapr/examples/unittesting/DaprWorkflowExampleTest.java +++ b/examples/src/main/java/io/dapr/examples/unittesting/DaprWorkflowExampleTest.java @@ -13,6 +13,7 @@ package io.dapr.examples.unittesting; +import com.microsoft.durabletask.Task; import com.microsoft.durabletask.TaskCanceledException; import io.dapr.workflows.Workflow; import io.dapr.workflows.WorkflowContext; @@ -20,13 +21,13 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.slf4j.Logger; -import reactor.core.publisher.Mono; import java.time.Duration; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.startsWith; +import static org.mockito.Mockito.mock; /** * 1. Build and install jars: @@ -49,7 +50,7 @@ public WorkflowStub create() { String name = ctx.getName(); String id = ctx.getInstanceId(); try { - ctx.waitForExternalEvent(name, Duration.ofMillis(100)).block(); + ctx.waitForExternalEvent(name, Duration.ofMillis(100)).await(); } catch (TaskCanceledException e) { ctx.getLogger().warn("Timed out"); } @@ -74,7 +75,7 @@ public void testWorkflow() { @Test public void testWorkflowWaitForEventTimeout() { WorkflowContext mockContext = createMockContext(timeoutWorkflow, workflowDefaultId); - Logger mockLogger = Mockito.mock(Logger.class); + Logger mockLogger = mock(Logger.class); Mockito.doReturn(mockLogger).when(mockContext).getLogger(); new DemoWorkflow().run(mockContext); @@ -85,7 +86,7 @@ public void testWorkflowWaitForEventTimeout() { @Test public void testWorkflowWaitForEventNoTimeout() { WorkflowContext mockContext = createMockContext(noTimeoutWorkflow, workflowDefaultId); - Logger mockLogger = Mockito.mock(Logger.class); + Logger mockLogger = mock(Logger.class); Mockito.doReturn(mockLogger).when(mockContext).getLogger(); new DemoWorkflow().run(mockContext); @@ -94,11 +95,11 @@ public void testWorkflowWaitForEventNoTimeout() { } private WorkflowContext createMockContext(String name, String id) { - WorkflowContext mockContext = Mockito.mock(WorkflowContext.class); + WorkflowContext mockContext = mock(WorkflowContext.class); Mockito.doReturn(name).when(mockContext).getName(); Mockito.doReturn(id).when(mockContext).getInstanceId(); - Mockito.doReturn(Mono.empty().then()) + Mockito.doReturn(mock(Task.class)) .when(mockContext).waitForExternalEvent(startsWith(noTimeoutWorkflow), any(Duration.class)); Mockito.doThrow(TaskCanceledException.class) .when(mockContext).waitForExternalEvent(startsWith(timeoutWorkflow), any(Duration.class)); diff --git a/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflow.java b/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflow.java index 6059e50f0b..f09954c47c 100644 --- a/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflow.java +++ b/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflow.java @@ -31,7 +31,7 @@ public WorkflowStub create() { ctx.getLogger().info("Instance ID: " + ctx.getInstanceId()); ctx.getLogger().info("Waiting for event: 'myEvent'..."); try { - ctx.waitForExternalEvent("myEvent", Duration.ofSeconds(10)).block(); + ctx.waitForExternalEvent("myEvent", Duration.ofSeconds(10)).await(); ctx.getLogger().info("Received!"); } catch (TaskCanceledException e) { ctx.getLogger().warn("Timed out"); diff --git a/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowWorker.java b/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowWorker.java index a51adf3656..f41fc4fb2d 100644 --- a/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowWorker.java +++ b/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowWorker.java @@ -34,7 +34,7 @@ public static void main(String[] args) throws Exception { // Build and then start the workflow runtime pulling and executing tasks try (WorkflowRuntime runtime = builder.build()) { System.out.println("Start workflow runtime"); - runtime.start().block(); + runtime.start(); } System.exit(0); diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/DaprWorkflowContextImpl.java b/sdk-workflows/src/main/java/io/dapr/workflows/DaprWorkflowContextImpl.java index 124f0d7c61..1210265635 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/DaprWorkflowContextImpl.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/DaprWorkflowContextImpl.java @@ -13,11 +13,11 @@ package io.dapr.workflows; +import com.microsoft.durabletask.Task; import com.microsoft.durabletask.TaskOrchestrationContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.helpers.NOPLogger; -import reactor.core.publisher.Mono; import java.time.Duration; @@ -88,9 +88,7 @@ public void complete(Object output) { /** * {@inheritDoc} */ - public Mono waitForExternalEvent(String eventName, Duration timeout) { - return Mono.fromRunnable(() -> { - this.innerContext.waitForExternalEvent(eventName, timeout).await(); - }); + public Task waitForExternalEvent(String eventName, Duration timeout) { + return this.innerContext.waitForExternalEvent(eventName, timeout); } } 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 9f482f360d..61b983de27 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowContext.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/WorkflowContext.java @@ -13,8 +13,8 @@ package io.dapr.workflows; +import com.microsoft.durabletask.Task; import org.slf4j.Logger; -import reactor.core.publisher.Mono; import java.time.Duration; @@ -62,7 +62,7 @@ public interface WorkflowContext { * External event names can be reused any number of times; they are not * required to be unique. * @param timeout The amount of time to wait before cancelling the external event task. - * @return A Mono Plan of type Void. + * @return An asynchronous durabletask.Task to await. */ - Mono waitForExternalEvent(String eventName, Duration timeout); + Task waitForExternalEvent(String eventName, Duration timeout); } diff --git a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowRuntime.java b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowRuntime.java index 09999f0f86..6754f675bd 100644 --- a/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowRuntime.java +++ b/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowRuntime.java @@ -14,7 +14,6 @@ package io.dapr.workflows.runtime; import com.microsoft.durabletask.DurableTaskGrpcWorker; -import reactor.core.publisher.Mono; /** * Contains methods to register workflows and activities. @@ -27,15 +26,25 @@ public WorkflowRuntime(DurableTaskGrpcWorker worker) { this.worker = worker; } + /** + * Start the Workflow runtime processing items and block. + * + */ + public void start() { + this.start(true); + } + /** * Start the Workflow runtime processing items. * - * @return A Mono Plan of type Void. + * @param block block the thread if true */ - public Mono start() { - return Mono.fromRunnable(() -> { + public void start(boolean block) { + if (block) { + this.worker.startAndBlock(); + } else { this.worker.start(); - }).then(); + } } /** diff --git a/sdk-workflows/src/test/java/io/dapr/workflows/runtime/DaprWorkflowContextImplTest.java b/sdk-workflows/src/test/java/io/dapr/workflows/DaprWorkflowContextImplTest.java similarity index 98% rename from sdk-workflows/src/test/java/io/dapr/workflows/runtime/DaprWorkflowContextImplTest.java rename to sdk-workflows/src/test/java/io/dapr/workflows/DaprWorkflowContextImplTest.java index 76030c1f30..2cdd1bada8 100644 --- a/sdk-workflows/src/test/java/io/dapr/workflows/runtime/DaprWorkflowContextImplTest.java +++ b/sdk-workflows/src/test/java/io/dapr/workflows/DaprWorkflowContextImplTest.java @@ -11,7 +11,7 @@ limitations under the License. */ -package io.dapr.workflows.runtime; +package io.dapr.workflows; import com.microsoft.durabletask.Task; import com.microsoft.durabletask.TaskOrchestrationContext; @@ -62,7 +62,7 @@ public void waitForExternalEventTest() { String expectedEvent = "TestEvent"; Duration expectedDuration = Duration.ofSeconds(1); - testContext.waitForExternalEvent(expectedEvent, expectedDuration).block(); + testContext.waitForExternalEvent(expectedEvent, expectedDuration).await(); verify(mockInnerContext, times(1)).waitForExternalEvent(expectedEvent, expectedDuration); } diff --git a/sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowRuntimeTest.java b/sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowRuntimeTest.java index 5fe84de1c4..63fdf42e48 100644 --- a/sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowRuntimeTest.java +++ b/sdk-workflows/src/test/java/io/dapr/workflows/runtime/WorkflowRuntimeTest.java @@ -20,8 +20,6 @@ import io.dapr.workflows.WorkflowStub; import org.junit.Test; -import java.time.Duration; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; public class WorkflowRuntimeTest { @@ -37,7 +35,7 @@ public void startTest() { DurableTaskGrpcWorker worker = new DurableTaskGrpcWorkerBuilder().build(); try (WorkflowRuntime runtime = new WorkflowRuntime(worker)) { assertDoesNotThrow(() -> { - runtime.start().block(Duration.ofSeconds(1)); + runtime.start(false); }); } }