From 8c0453e73cbd5f403b9c0ae4a4c21daecc1a788b Mon Sep 17 00:00:00 2001 From: Francisco Javier Tirado Sarti Date: Wed, 29 Jan 2025 20:50:37 +0100 Subject: [PATCH 1/3] [Fix #520] Updating readme Signed-off-by: Francisco Javier Tirado Sarti --- README.md | 54 +++-- examples/events/pom.xml | 19 ++ .../src/main/java/events/EventExample.java | 50 +++++ examples/events/src/main/resources/emit.yaml | 14 ++ .../events/src/main/resources/listen.yaml | 13 ++ examples/pom.xml | 33 +++ {impl/bom => examples/simpleGet}/pom.xml | 9 +- .../impl/BlockingExample.java | 38 ++++ .../impl/NotBlockingExample.java | 37 ++++ .../simpleGet/src/main/resources/get.yaml | 11 + impl/README.md | 189 ++++++++++++++++++ .../impl/DefaultExecutorServiceFactory.java | 39 ++++ .../impl/WorkflowApplication.java | 11 +- .../impl/WorkflowDefinition.java | 10 +- .../impl/WorkflowInstance.java | 2 +- .../impl/events/InMemoryEvents.java | 4 +- .../impl/executors/ListenExecutor.java | 3 + .../impl/executors/TaskExecutorHelper.java | 2 +- .../impl/executors/WaitExecutor.java | 9 +- .../impl/EventDefinitionTest.java | 8 +- .../impl/executors/HttpExecutor.java | 20 +- impl/pom.xml | 1 - pom.xml | 1 + 23 files changed, 528 insertions(+), 49 deletions(-) create mode 100644 examples/events/pom.xml create mode 100644 examples/events/src/main/java/events/EventExample.java create mode 100644 examples/events/src/main/resources/emit.yaml create mode 100644 examples/events/src/main/resources/listen.yaml create mode 100644 examples/pom.xml rename {impl/bom => examples/simpleGet}/pom.xml (76%) create mode 100644 examples/simpleGet/src/main/java/io/serverlessworkflow/impl/BlockingExample.java create mode 100644 examples/simpleGet/src/main/java/io/serverlessworkflow/impl/NotBlockingExample.java create mode 100644 examples/simpleGet/src/main/resources/get.yaml create mode 100644 impl/README.md create mode 100644 impl/core/src/main/java/io/serverlessworkflow/impl/DefaultExecutorServiceFactory.java diff --git a/README.md b/README.md index c0b4df70..ee86fa29 100644 --- a/README.md +++ b/README.md @@ -8,11 +8,11 @@ Provides the Java API for the [Serverless Workflow Specification](https://github With the SDK you can: * Read workflow JSON and YAML definitions -* Write workflow in JSON and YAML format. +* Write workflow definition in JSON and YAML format. +* Test your workflow definitions using the reference implementation. -Serverless Workflow Java SDK is **not** a workflow runtime implementation but can be used by Java runtime implementations to parse workflow definitions. -### Status +## Status | Latest Releases | Conformance to spec version | | :---: | :---: | @@ -25,17 +25,17 @@ Serverless Workflow Java SDK is **not** a workflow runtime implementation but ca Note that 6.0.0.Final, which will be the one for specification version 0.9, is skipped intentionally in case someone want to work on it. -### JDK Version +## JDK Version | SDK Version | JDK Version | | :---: | :---: | | 5.0.0 and after | 11 | | 4.0.x and before | 8 | -### Getting Started +## Getting Started -#### Building SNAPSHOT locally +### Building SNAPSHOT locally To build project and run tests locally: @@ -47,7 +47,7 @@ mvn clean install The project uses [Google's code styleguide](https://google.github.io/styleguide/javaguide.html). Your changes should be automatically formatted during the build. -#### Maven projects: +### Maven projects: Add the following dependencies to your pom.xml `dependencies` section: @@ -59,7 +59,7 @@ Add the following dependencies to your pom.xml `dependencies` section: ``` -#### Gradle projects: +### Gradle projects: Add the following dependencies to your build.gradle `dependencies` section: @@ -67,11 +67,20 @@ Add the following dependencies to your pom.xml `dependencies` section: implementation("io.serverlessworkflow:serverlessworkflow-api:7.0.0-SNAPSHOT") ``` -### How to Use +## How to Use -#### Creating from JSON/YAML source +There are, roughly speaking, two kind of users of this SDK: + * Those ones interested on implementing their own runtime using Java. + * Those ones interested on using the provided runtime reference implementation. -You can create a Workflow instance from JSON/YAML source: +### Implementing your own runtime + +For those ones interested on implementing their own runtime, this SDK provides an easy way to load an in memory representation of a given workflow definition. +This memory representation consist of a hierarchy of POJOS directly generated from the Serverless Workflow specification [schema](api/src/main/resources/schema/workflow.yaml), which ensures the internal representation is aligned with the specification schema. The root of the hierarchy is `io.serverlessworkflow.api.types.Workflow` class + +#### Reading workflow definition from JSON/YAML source + +You can read a Workflow definition from JSON/YAML source: Let's say you have a simple YAML based workflow definition in a file name `simple.yaml` located in your working dir: @@ -93,7 +102,7 @@ do: ``` -To parse it and create a Workflow instance you can do: +To parse it and get a Workflow instance you can do: ``` java @@ -102,10 +111,20 @@ try (InputStream in = new FileInputStream("simple.yaml")) { // Once you have the Workflow instance you can use its API to inspect it } ``` +By default, Workflows are not validated against the schema (performance being the priority). If you want to enable validation, you can do that by using: -#### Writing a workflow +``` java +try (InputStream in = new FileInputStream("simple.yaml")) { + Workflow workflow = WorkflowReader.validation().readWorkflow (in, WorkflowFormat.YAML); + // Once you have the Workflow instance you can use its API to inspect it +} +``` + +For additional reading helper methods, including the one to read a workflow definition from classpath, check [WorkflowReader](api/src/main/java/io/serverlessworkflow/api/WorkflowReader.java) class. + +#### Writing workflow definition to a a JSON/YAML target -Given a workflow definition, you can store it using JSON or YAML format. +Given a Workflow instance, you can store it using JSON or YAML format. For example, to store a workflow using json format in a file called `simple.json`, you write ``` java @@ -113,4 +132,9 @@ try (OutputStream out = new FileOutputStream("simple.json")) { WorkflowWriter.writeWorkflow(out, workflow, WorkflowFormat.JSON); } -``` \ No newline at end of file +``` +For additional writing helper methods, check [WorkflowWriter](api/src/main/java/io/serverlessworkflow/api/WorkflowWriter.java) class. + +### Reference implementation +See Reference implementation [readme](impl/README.md). + diff --git a/examples/events/pom.xml b/examples/events/pom.xml new file mode 100644 index 00000000..245459ed --- /dev/null +++ b/examples/events/pom.xml @@ -0,0 +1,19 @@ + + 4.0.0 + + io.serverlessworkflow + serverlessworkflow-examples + 7.0.0-SNAPSHOT + + events + + + io.serverlessworkflow + serverlessworkflow-impl-core + + + org.slf4j + slf4j-simple + + + \ No newline at end of file diff --git a/examples/events/src/main/java/events/EventExample.java b/examples/events/src/main/java/events/EventExample.java new file mode 100644 index 00000000..628782fb --- /dev/null +++ b/examples/events/src/main/java/events/EventExample.java @@ -0,0 +1,50 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification 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 events; + +import io.serverlessworkflow.api.WorkflowReader; +import io.serverlessworkflow.impl.WorkflowApplication; +import io.serverlessworkflow.impl.WorkflowDefinition; +import io.serverlessworkflow.impl.WorkflowInstance; +import java.io.IOException; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class EventExample { + + private static final Logger logger = LoggerFactory.getLogger(EventExample.class); + + public static void main(String[] args) throws IOException { + try (WorkflowApplication appl = WorkflowApplication.builder().build()) { + WorkflowDefinition listenDefinition = + appl.workflowDefinition(WorkflowReader.readWorkflowFromClasspath("listen.yaml")); + WorkflowDefinition emitDefinition = + appl.workflowDefinition(WorkflowReader.readWorkflowFromClasspath("emit.yaml")); + WorkflowInstance waitingInstance = listenDefinition.instance(Map.of()); + waitingInstance + .start() + .thenAccept(node -> logger.info("Waiting instance completed with result {}", node)); + logger.info("Listen instance waiting for proper event, Status {}", waitingInstance.status()); + logger.info("Publishing event with temperature 35"); + emitDefinition.instance(Map.of("temperature", 35)).start().join(); + logger.info( + "Listen instance still waiting for proper event, Status {}", waitingInstance.status()); + logger.info("Publishing event with temperature 39"); + emitDefinition.instance(Map.of("temperature", 39)).start().join(); + } + } +} diff --git a/examples/events/src/main/resources/emit.yaml b/examples/events/src/main/resources/emit.yaml new file mode 100644 index 00000000..4d14b030 --- /dev/null +++ b/examples/events/src/main/resources/emit.yaml @@ -0,0 +1,14 @@ +document: + dsl: '1.0.0-alpha5' + namespace: test + name: emit + version: '0.1.0' +do: + - emitEvent: + emit: + event: + with: + source: https://hospital.com + type: com.fake-hospital.vitals.measurements.temperature + data: + temperature: ${.temperature} \ No newline at end of file diff --git a/examples/events/src/main/resources/listen.yaml b/examples/events/src/main/resources/listen.yaml new file mode 100644 index 00000000..e49cea92 --- /dev/null +++ b/examples/events/src/main/resources/listen.yaml @@ -0,0 +1,13 @@ +document: + dsl: '1.0.0-alpha5' + namespace: examples + name: listen + version: '0.1.0' +do: + - callDoctor: + listen: + to: + one: + with: + type: com.fake-hospital.vitals.measurements.temperature + data: ${ .temperature > 38 } \ No newline at end of file diff --git a/examples/pom.xml b/examples/pom.xml new file mode 100644 index 00000000..54c88571 --- /dev/null +++ b/examples/pom.xml @@ -0,0 +1,33 @@ + + 4.0.0 + + io.serverlessworkflow + serverlessworkflow-parent + 7.0.0-SNAPSHOT + + serverlessworkflow-examples + pom + + + + io.serverlessworkflow + serverlessworkflow-impl-core + ${project.version} + + + io.serverlessworkflow + serverlessworkflow-impl-http + ${project.version} + + + org.slf4j + slf4j-simple + 2.0.16 + + + + + simpleGet + events + + \ No newline at end of file diff --git a/impl/bom/pom.xml b/examples/simpleGet/pom.xml similarity index 76% rename from impl/bom/pom.xml rename to examples/simpleGet/pom.xml index 63ef0fe3..34ad62c7 100644 --- a/impl/bom/pom.xml +++ b/examples/simpleGet/pom.xml @@ -2,11 +2,10 @@ 4.0.0 io.serverlessworkflow - serverlessworkflow-impl + serverlessworkflow-examples 7.0.0-SNAPSHOT - serverlessworkflow-impl-bom - pom + simpleGet io.serverlessworkflow @@ -16,5 +15,9 @@ io.serverlessworkflow serverlessworkflow-impl-http + + org.slf4j + slf4j-simple + \ No newline at end of file diff --git a/examples/simpleGet/src/main/java/io/serverlessworkflow/impl/BlockingExample.java b/examples/simpleGet/src/main/java/io/serverlessworkflow/impl/BlockingExample.java new file mode 100644 index 00000000..233d121f --- /dev/null +++ b/examples/simpleGet/src/main/java/io/serverlessworkflow/impl/BlockingExample.java @@ -0,0 +1,38 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification 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.serverlessworkflow.impl; + +import io.serverlessworkflow.api.WorkflowReader; +import java.io.IOException; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BlockingExample { + + private static final Logger logger = LoggerFactory.getLogger(BlockingExample.class); + + public static void main(String[] args) throws IOException { + try (WorkflowApplication appl = WorkflowApplication.builder().build()) { + logger.info( + "Workflow output is {}", + appl.workflowDefinition(WorkflowReader.readWorkflowFromClasspath("get.yaml")) + .instance(Map.of("petId", 10)) + .start() + .join()); + } + } +} diff --git a/examples/simpleGet/src/main/java/io/serverlessworkflow/impl/NotBlockingExample.java b/examples/simpleGet/src/main/java/io/serverlessworkflow/impl/NotBlockingExample.java new file mode 100644 index 00000000..cb663c1a --- /dev/null +++ b/examples/simpleGet/src/main/java/io/serverlessworkflow/impl/NotBlockingExample.java @@ -0,0 +1,37 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification 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.serverlessworkflow.impl; + +import io.serverlessworkflow.api.WorkflowReader; +import java.io.IOException; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class NotBlockingExample { + + private static final Logger logger = LoggerFactory.getLogger(NotBlockingExample.class); + + public static void main(String[] args) throws IOException { + try (WorkflowApplication appl = WorkflowApplication.builder().build()) { + appl.workflowDefinition(WorkflowReader.readWorkflowFromClasspath("get.yaml")) + .instance(Map.of("petId", 10)) + .start() + .thenAccept(node -> logger.info("Workflow output is {}", node)); + logger.info("The request has been sent, this thread might continue doing stuff"); + } + } +} diff --git a/examples/simpleGet/src/main/resources/get.yaml b/examples/simpleGet/src/main/resources/get.yaml new file mode 100644 index 00000000..7adf3132 --- /dev/null +++ b/examples/simpleGet/src/main/resources/get.yaml @@ -0,0 +1,11 @@ +document: + dsl: '1.0.0-alpha5' + namespace: examples + name: call-http-shorthand-endpoint + version: '0.1.0' +do: + - getPet: + call: http + with: + method: get + endpoint: https://petstore.swagger.io/v2/pet/{petId} diff --git a/impl/README.md b/impl/README.md new file mode 100644 index 00000000..ebf09cc8 --- /dev/null +++ b/impl/README.md @@ -0,0 +1,189 @@ +![Verify JAVA SDK](https://github.com/serverlessworkflow/sdk-java/workflows/Verify%20JAVA%20SDK/badge.svg) +![Deploy JAVA SDK](https://github.com/serverlessworkflow/sdk-java/workflows/Deploy%20JAVA%20SDK/badge.svg) [![Gitpod ready-to-code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/serverlessworkflow/sdk-java) + +# Serverless Workflow Specification - Java SDK- Reference Implementation + +Welcome to Java SDK runtime reference implementation, a lightweight implementation of the Serverless Workflow specification which provides a simple, non blocking, reactive API for workflow execution. + +Although initially conceived mainly for testing purposes, it was designed to be easily expanded, so it can eventually become production ready. + +## Status. + +This reference implementation is currently capable of running workflows consisting of: + + +* Tasks + * Switch + * Set + * Do + * Raise + * Listen + * Emit + * Fork + * For + * Try + * Wait + * Call + * HTTP +* Schema Validation + * Input + * Output +* Expressions + * Input + * Output + * Export + * Special keywords: runtime, workflow, task... +* Error definitions + + +## Setup + +Serverless workflow reference implementation only requires setting up Java and Maven/Gradle. + +### JDK Version + +Reference implementation requires [Java 17](https://openjdk.org/projects/jdk/17/) or newer versions. + +### Dependencies + +One of the goals of the reference implementation is to maintain the number of dependencies as lower as possible. With that spirit, a modular approach has been followed, letting the users decide, depending on their workflows nature, which dependencies should be include. + +In practical terms, this means a separation between the core part and additional dependencies that should be explicitly included if your workflow is interacting with an external service that communicated using a particular technology supported by the specification (at this moment, just HTTP). The intention of this is to avoid adding dependencies that you do not really need (for example, when gRPC call will be implemented, if we were adding the gRPC stack to the core dependencies, you wont be able to get rid of it even if none of your workflows use it) + +#### Maven + +You always need to add this dependency to your pom.xml `dependencies` section: + +```xml + + io.serverlessworkflow + serverlessworkflow-impl-core + 7.0.0-SNAPSHOT + +``` + +And only if your workflow is using HTTP calls, you must add: + +```xml + + io.serverlessworkflow + serverlessworkflow-impl-http + 7.0.0-SNAPSHOT + +``` + + + +### Gradle projects: + +You always need to add this dependency to your build.gradle `dependencies` section: + +```text +implementation("io.serverlessworkflow:serverlessworkflow-impl-core:7.0.0-SNAPSHOT") +``` + +And only if your workflow is using HTTP calls, you must add: + +```text +implementation("io.serverlessworkflow:serverlessworkflow-impl-http:7.0.0-SNAPSHOT") +``` + + +## How to use + +Quick version is intended for impatient users that want to try something as soon as possible. + +Detailed version is more suitable for those users interested on a more thoughtful discussion of the API. + +### Quick version + +For a quick introduction, we are going to use a simple workflow [definition](../examples/simpleGet/src/main/resources/get.yaml) that performs a get call. +We are going to show two ways of invoking the workflow: + - blocking the thread till the get request goes through + - returning control to the caller, so the main thread continues while the get is executed + +In order to execute the workflow, blocking the thread till the http request is completed, you should write + +``` java +try (WorkflowApplication appl = WorkflowApplication.builder().build()) { + logger.info( + "Workflow output is {}", + appl.workflowDefinition(WorkflowReader.readWorkflowFromClasspath("get.yaml")) + .instance(Map.of("petId", 10)) + .start() + .join()); + } +``` +You can find the complete java code [here](../examples/simpleGet/src/main/java/BlockingExample.java) + +In order to execute the workflow, without blocking the calling thread till the http request is completed, you should write + +``` java + try (WorkflowApplication appl = WorkflowApplication.builder().build()) { + appl.workflowDefinition(WorkflowReader.readWorkflowFromClasspath("get.yaml")) + .instance(Map.of("petId", 10)) + .start() + .thenAccept(node -> logger.info("Workflow output is {}", node)); + } +``` +When the http request is done, both examples will print a similar output + +`Workflow output is {"id":10,"category":{"id":10,"name":"string"},"name":"doggie","photoUrls":["string"],"tags":[{"id":10,"name":"string"}],"status":"string"}` + +You can find the complete java code [here](../examples/simpleGet/src/main/java/NotBlockingExample.java) + +### Detailed version + +To discuss runtime API we are going to use a couple of workflow: +- [listen.yaml](../examples/events/src/main/listen.yaml), which waits for an event reporting a temperature greater than 38 +- [emit.yaml](../examples/events/src/main/emit.yaml), which emits events with a certain temperature, specified as workflow parameter. + +A brief summary of what we are trying to do. We will start listen.yaml, which will complete when it receives an event with the proper temperature, but it wont block the main thread while waiting for it. Then, we will send an event with a lower temperature, that will be ignored. And finally, we will send an event with a greater temperature, that will complete the waiting workflow. + +The first step is to create a [WorkflowApplication](core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java) instance. An application is an abstraction that allow customization of different aspect of the workflow execution (for example change the default `ExecutorService` for thread spawning) + +Since `WorkflowApplication` implements `Autocloseable`, we better use a try...finally block, ensuring any resource that might have been used by the workflow is freed when done. + +`try (WorkflowApplication appl = WorkflowApplication.builder().build())` + +Once we have the application object, we use it to parse our definition examples. To load each workflow definition, we use `readFromClasspath` helper method defined in [WorkflowReader](api/src/main/java/io/serverlessworkflow/api/WorkflowReader.java) class. + +```java + WorkflowDefinition listenDefinition = + appl.workflowDefinition(WorkflowReader.readWorkflowFromClasspath("listen.yaml")); + WorkflowDefinition emitDefinition = + appl.workflowDefinition(WorkflowReader.readWorkflowFromClasspath("emit.yaml")); +``` + +A [WorkflowDefinition](core/src/main/java/io/serverlessworkflow/impl/WorkflowDefinition.java) object is immutable and therefore thread safe. It is used to execute as many workflow instances as desired. + +To execute a workflow, we first create a [WorkflowInstance](core/src/main/java/io/serverlessworkflow/impl/WorkflowInstance.java) object (the initial status is PENDING) and then invoke `start` method on it (the status is changed to RUNNING). `start` method returns a [CompletableFuture](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html), which we use to indicate that a log message should be printed when the workflow is completed. + +```java + WorkflowInstance waitingInstance = listenDefinition.instance(Map.of()); + waitingInstance + .start() + .thenAccept(node -> logger.info("Waiting instance completed with result {}", node)); +``` + +The next line will be executed as soon as the workflow execution starts waiting for events to arrive, moment at which control is returned to the calling thread. Therefore, we can execute another workflow instance while the first one is waiting. + +We are going to send an event with a temperature that does not satisfy the criteria, so the listen instance will continue waiting. To pass parameters to the workflow instance that sends the event, we use a regular Java `Map`. Notice that, since we want to wait till the event is published before executing the next line, we call `join` after `start`, telling the `CompletableFuture` to wait for workflow completion. + +```java + emitDefinition.instance(Map.of("temperature", 35)).start().join(); + ``` + + Now its time to complete the waiting instance and send an event with the expected temperature. We do so by reusing `emitDefinition`. + +```java + emitDefinition.instance(Map.of("temperature", 39)).start().join(); + ``` + +After that, listen instance will be completed and we will see this log message + +```java +[pool-1-thread-1] INFO events.EventExample - Waiting instance completed with result [{"temperature":39}] +``` +The source code of the example is here (../examples/events/src/main/java/EventExample.java) + diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/DefaultExecutorServiceFactory.java b/impl/core/src/main/java/io/serverlessworkflow/impl/DefaultExecutorServiceFactory.java new file mode 100644 index 00000000..1ac1f759 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/DefaultExecutorServiceFactory.java @@ -0,0 +1,39 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification 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.serverlessworkflow.impl; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class DefaultExecutorServiceFactory implements ExecutorServiceFactory { + + private static final ExecutorServiceFactory instance = new DefaultExecutorServiceFactory(); + + public static ExecutorServiceFactory instance() { + return instance; + } + + private static class ExecutorServiceHolder { + private static ExecutorService instance = Executors.newCachedThreadPool(); + } + + @Override + public ExecutorService get() { + return ExecutorServiceHolder.instance; + } + + private DefaultExecutorServiceFactory() {} +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java index 23597057..b998c57d 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java @@ -53,8 +53,6 @@ public class WorkflowApplication implements AutoCloseable { private final EventConsumer eventConsumer; private final EventPublisher eventPublisher; - private ExecutorService executorService; - private WorkflowApplication(Builder builder) { this.taskFactory = builder.taskFactory; this.exprFactory = builder.exprFactory; @@ -193,7 +191,7 @@ public WorkflowDefinition workflowDefinition(Workflow workflow) { } @Override - public void close() throws Exception { + public void close() { for (WorkflowDefinition definition : definitions.values()) { definition.close(); } @@ -214,11 +212,6 @@ public EventConsumer eventConsumer() { } public ExecutorService executorService() { - synchronized (executorFactory) { - if (executorService == null) { - executorService = executorFactory.get(); - } - } - return executorService; + return executorFactory.get(); } } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowDefinition.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowDefinition.java index 6566cf6b..1a789616 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowDefinition.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowDefinition.java @@ -77,15 +77,15 @@ public WorkflowInstance instance(Object input) { return new WorkflowInstance(this, JsonUtils.fromValue(input)); } - public Optional inputSchemaValidator() { + Optional inputSchemaValidator() { return inputSchemaValidator; } - public TaskExecutor startTask() { + TaskExecutor startTask() { return taskExecutor; } - public Optional inputFilter() { + Optional inputFilter() { return inputFilter; } @@ -97,11 +97,11 @@ public Collection listeners() { return application.listeners(); } - public Optional outputFilter() { + Optional outputFilter() { return outputFilter; } - public Optional outputSchemaValidator() { + Optional outputSchemaValidator() { return outputSchemaValidator; } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowInstance.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowInstance.java index 2e55c484..83cb9030 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowInstance.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowInstance.java @@ -68,7 +68,7 @@ private JsonNode whenCompleted(JsonNode node) { .map(f -> f.apply(workflowContext, null, node)) .orElse(node); workflowContext.definition().outputSchemaValidator().ifPresent(v -> v.validate(output)); - status.compareAndSet(WorkflowStatus.RUNNING, WorkflowStatus.COMPLETED); + status.set(WorkflowStatus.COMPLETED); completedAt = Instant.now(); return output; } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/events/InMemoryEvents.java b/impl/core/src/main/java/io/serverlessworkflow/impl/events/InMemoryEvents.java index 3993f8fe..714d89d0 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/events/InMemoryEvents.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/events/InMemoryEvents.java @@ -16,6 +16,7 @@ package io.serverlessworkflow.impl.events; import io.cloudevents.CloudEvent; +import io.serverlessworkflow.impl.DefaultExecutorServiceFactory; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; @@ -62,7 +63,8 @@ public CompletableFuture publish(CloudEvent ce) { if (consumer != null) { consumer.accept(ce); } - }); + }, + DefaultExecutorServiceFactory.instance().get()); } @Override diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/ListenExecutor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/ListenExecutor.java index 1b53dea3..58472289 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/ListenExecutor.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/ListenExecutor.java @@ -35,6 +35,7 @@ import io.serverlessworkflow.impl.WorkflowContext; import io.serverlessworkflow.impl.WorkflowFilter; import io.serverlessworkflow.impl.WorkflowPosition; +import io.serverlessworkflow.impl.WorkflowStatus; import io.serverlessworkflow.impl.WorkflowUtils; import io.serverlessworkflow.impl.events.CloudEventUtils; import io.serverlessworkflow.impl.events.EventConsumer; @@ -235,6 +236,7 @@ protected CompletableFuture internalExecute( WorkflowContext workflow, TaskContext taskContext) { ArrayNode output = JsonUtils.mapper().createArrayNode(); Collection registrations = new ArrayList<>(); + workflow.instance().status(WorkflowStatus.WAITING); return buildFuture( regBuilders, registrations, @@ -243,6 +245,7 @@ protected CompletableFuture internalExecute( processCe(converter.apply(ce), output, workflow, taskContext, future))) .thenApply( v -> { + workflow.instance().status(WorkflowStatus.RUNNING); registrations.forEach(reg -> eventConsumer.unregister(reg)); return output; }); diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/TaskExecutorHelper.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/TaskExecutorHelper.java index ba6c33b2..3fa77f5f 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/TaskExecutorHelper.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/TaskExecutorHelper.java @@ -54,7 +54,7 @@ public static boolean isActive(WorkflowContext context) { } public static boolean isActive(WorkflowStatus status) { - return status == WorkflowStatus.RUNNING; + return status == WorkflowStatus.RUNNING || status == WorkflowStatus.WAITING; } public static TaskExecutor createExecutorList( diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/WaitExecutor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/WaitExecutor.java index 2f1ea1b6..42e648aa 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/WaitExecutor.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/WaitExecutor.java @@ -23,6 +23,7 @@ import io.serverlessworkflow.impl.WorkflowApplication; import io.serverlessworkflow.impl.WorkflowContext; import io.serverlessworkflow.impl.WorkflowPosition; +import io.serverlessworkflow.impl.WorkflowStatus; import io.serverlessworkflow.impl.executors.RegularTaskExecutor.RegularTaskExecutorBuilder; import io.serverlessworkflow.impl.resources.ResourceLoader; import java.time.Duration; @@ -72,7 +73,13 @@ protected WaitExecutor(WaitExecutorBuilder builder) { @Override protected CompletableFuture internalExecute( WorkflowContext workflow, TaskContext taskContext) { + workflow.instance().status(WorkflowStatus.WAITING); return new CompletableFuture() - .completeOnTimeout(taskContext.output(), millisToWait.toMillis(), TimeUnit.MILLISECONDS); + .completeOnTimeout(taskContext.output(), millisToWait.toMillis(), TimeUnit.MILLISECONDS) + .thenApply( + node -> { + workflow.instance().status(WorkflowStatus.RUNNING); + return node; + }); } } diff --git a/impl/core/src/test/java/io/serverlessworkflow/impl/EventDefinitionTest.java b/impl/core/src/test/java/io/serverlessworkflow/impl/EventDefinitionTest.java index 20f78b65..981b149d 100644 --- a/impl/core/src/test/java/io/serverlessworkflow/impl/EventDefinitionTest.java +++ b/impl/core/src/test/java/io/serverlessworkflow/impl/EventDefinitionTest.java @@ -51,7 +51,7 @@ void testEventListened(String listen, String emit, JsonNode expectedResult, Obje appl.workflowDefinition(WorkflowReader.readWorkflowFromClasspath(emit)); WorkflowInstance waitingInstance = listenDefinition.instance(Map.of()); CompletableFuture future = waitingInstance.start(); - assertThat(waitingInstance.status()).isEqualTo(WorkflowStatus.RUNNING); + assertThat(waitingInstance.status()).isEqualTo(WorkflowStatus.WAITING); emitDefinition.instance(emitInput).start().join(); assertThat(future).isCompleted(); assertThat(waitingInstance.status()).isEqualTo(WorkflowStatus.COMPLETED); @@ -70,11 +70,11 @@ void testEventsListened(String listen, String emit1, String emit2, JsonNode expe appl.workflowDefinition(WorkflowReader.readWorkflowFromClasspath(emit2)); WorkflowInstance waitingInstance = listenDefinition.instance(Map.of()); CompletableFuture future = waitingInstance.start(); - assertThat(waitingInstance.status()).isEqualTo(WorkflowStatus.RUNNING); + assertThat(waitingInstance.status()).isEqualTo(WorkflowStatus.WAITING); emitDoctorDefinition.instance(Map.of("temperature", 35)).start().join(); - assertThat(waitingInstance.status()).isEqualTo(WorkflowStatus.RUNNING); + assertThat(waitingInstance.status()).isEqualTo(WorkflowStatus.WAITING); emitDoctorDefinition.instance(Map.of("temperature", 39)).start().join(); - assertThat(waitingInstance.status()).isEqualTo(WorkflowStatus.RUNNING); + assertThat(waitingInstance.status()).isEqualTo(WorkflowStatus.WAITING); emitOutDefinition.instance(Map.of()).start().join(); assertThat(future).isCompleted(); assertThat(waitingInstance.status()).isEqualTo(WorkflowStatus.COMPLETED); diff --git a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/HttpExecutor.java b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/HttpExecutor.java index 79a57156..3c078309 100644 --- a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/HttpExecutor.java +++ b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/HttpExecutor.java @@ -107,14 +107,18 @@ public CompletableFuture apply( Builder request = target.request(); ExpressionUtils.evaluateExpressionMap(headersMap, workflow, taskContext, input) .forEach(request::header); - try { - return CompletableFuture.completedFuture( - requestFunction.apply(request, workflow, taskContext, input)); - } catch (WebApplicationException exception) { - throw new WorkflowException( - WorkflowError.communication(exception.getResponse().getStatus(), taskContext, exception) - .build()); - } + return CompletableFuture.supplyAsync( + () -> { + try { + return requestFunction.apply(request, workflow, taskContext, input); + } catch (WebApplicationException exception) { + throw new WorkflowException( + WorkflowError.communication( + exception.getResponse().getStatus(), taskContext, exception) + .build()); + } + }, + workflow.definition().application().executorService()); } @Override diff --git a/impl/pom.xml b/impl/pom.xml index 0f0a224d..d2efb4f7 100644 --- a/impl/pom.xml +++ b/impl/pom.xml @@ -60,6 +60,5 @@ http core - bom \ No newline at end of file diff --git a/pom.xml b/pom.xml index 28314d62..27ef242f 100644 --- a/pom.xml +++ b/pom.xml @@ -40,6 +40,7 @@ api custom-generator impl + examples From b98e1129aafa13a6f873045c49bc05fd428c73ed Mon Sep 17 00:00:00 2001 From: Francisco Javier Tirado Sarti <65240126+fjtirado@users.noreply.github.com> Date: Thu, 30 Jan 2025 21:39:16 +0100 Subject: [PATCH 2/3] Update impl/README.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ricardo Zanini <1538000+ricardozanini@users.noreply.github.com> Update impl/README.md Co-authored-by: Ricardo Zanini <1538000+ricardozanini@users.noreply.github.com> Update impl/README.md Co-authored-by: Ricardo Zanini <1538000+ricardozanini@users.noreply.github.com> Update impl/README.md Co-authored-by: Ricardo Zanini <1538000+ricardozanini@users.noreply.github.com> Update impl/README.md Co-authored-by: Ricardo Zanini <1538000+ricardozanini@users.noreply.github.com> Update impl/README.md Co-authored-by: Ricardo Zanini <1538000+ricardozanini@users.noreply.github.com> Update impl/README.md Co-authored-by: Ricardo Zanini <1538000+ricardozanini@users.noreply.github.com> Update impl/README.md Co-authored-by: Ricardo Zanini <1538000+ricardozanini@users.noreply.github.com> Update impl/README.md Co-authored-by: Ricardo Zanini <1538000+ricardozanini@users.noreply.github.com> Update impl/README.md Co-authored-by: Ricardo Zanini <1538000+ricardozanini@users.noreply.github.com> Update impl/README.md Co-authored-by: Ricardo Zanini <1538000+ricardozanini@users.noreply.github.com> Update impl/README.md Co-authored-by: Ricardo Zanini <1538000+ricardozanini@users.noreply.github.com> Update impl/README.md Co-authored-by: Ricardo Zanini <1538000+ricardozanini@users.noreply.github.com> Update impl/README.md Co-authored-by: Ricardo Zanini <1538000+ricardozanini@users.noreply.github.com> Update impl/README.md Co-authored-by: Ricardo Zanini <1538000+ricardozanini@users.noreply.github.com> Update impl/README.md Co-authored-by: Ricardo Zanini <1538000+ricardozanini@users.noreply.github.com> [Fix #520] More comments Update README.md Co-authored-by: Gonzalo Muñoz Update impl/README.md Co-authored-by: Gonzalo Muñoz Update impl/README.md Co-authored-by: Gonzalo Muñoz Update README.md Co-authored-by: Gonzalo Muñoz Update README.md Co-authored-by: Gonzalo Muñoz Update README.md Co-authored-by: Gonzalo Muñoz Update impl/README.md Co-authored-by: Gonzalo Muñoz Signed-off-by: Francisco Javier Tirado Sarti --- README.md | 12 +++++---- impl/README.md | 71 +++++++++++++++++++++++++++----------------------- 2 files changed, 45 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index ee86fa29..b641b483 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Provides the Java API for the [Serverless Workflow Specification](https://github With the SDK you can: * Read workflow JSON and YAML definitions -* Write workflow definition in JSON and YAML format. +* Write workflow definitions in JSON and YAML formats. * Test your workflow definitions using the reference implementation. @@ -29,6 +29,7 @@ Note that 6.0.0.Final, which will be the one for specification version 0.9, is s | SDK Version | JDK Version | | :---: | :---: | +| 7.0.0 and after | 17 | | 5.0.0 and after | 11 | | 4.0.x and before | 8 | @@ -76,9 +77,9 @@ There are, roughly speaking, two kind of users of this SDK: ### Implementing your own runtime For those ones interested on implementing their own runtime, this SDK provides an easy way to load an in memory representation of a given workflow definition. -This memory representation consist of a hierarchy of POJOS directly generated from the Serverless Workflow specification [schema](api/src/main/resources/schema/workflow.yaml), which ensures the internal representation is aligned with the specification schema. The root of the hierarchy is `io.serverlessworkflow.api.types.Workflow` class +This in-memory representation consists of a hierarchy of POJOS directly generated from the Serverless Workflow specification [schema](api/src/main/resources/schema/workflow.yaml), which ensures the internal representation is aligned with the specification schema. The root of the hierarchy is `io.serverlessworkflow.api.types.Workflow` class -#### Reading workflow definition from JSON/YAML source +### Reading workflow definition from JSON/YAML source You can read a Workflow definition from JSON/YAML source: @@ -122,7 +123,7 @@ try (InputStream in = new FileInputStream("simple.yaml")) { For additional reading helper methods, including the one to read a workflow definition from classpath, check [WorkflowReader](api/src/main/java/io/serverlessworkflow/api/WorkflowReader.java) class. -#### Writing workflow definition to a a JSON/YAML target +### Writing workflow definition to a JSON/YAML target Given a Workflow instance, you can store it using JSON or YAML format. For example, to store a workflow using json format in a file called `simple.json`, you write @@ -136,5 +137,6 @@ try (OutputStream out = new FileOutputStream("simple.json")) { For additional writing helper methods, check [WorkflowWriter](api/src/main/java/io/serverlessworkflow/api/WorkflowWriter.java) class. ### Reference implementation -See Reference implementation [readme](impl/README.md). + +The reference implementation provides a ready-to-use runtime that supports the Serverless Workflow Specification. It includes a workflow execution engine, validation utilities, and illustrative examples to help you quickly test and deploy your workflows. For details on usage, configuration, and supported features, see [readme](impl/README.md). diff --git a/impl/README.md b/impl/README.md index ebf09cc8..f581a398 100644 --- a/impl/README.md +++ b/impl/README.md @@ -7,7 +7,7 @@ Welcome to Java SDK runtime reference implementation, a lightweight implementati Although initially conceived mainly for testing purposes, it was designed to be easily expanded, so it can eventually become production ready. -## Status. +## Status This reference implementation is currently capable of running workflows consisting of: @@ -38,17 +38,18 @@ This reference implementation is currently capable of running workflows consisti ## Setup -Serverless workflow reference implementation only requires setting up Java and Maven/Gradle. +Before getting started, ensure you have Java 17+ and Maven or Gradle installed. -### JDK Version - -Reference implementation requires [Java 17](https://openjdk.org/projects/jdk/17/) or newer versions. +Install [Java 17](https://openjdk.org/projects/jdk/17/) +Install [Maven](https://maven.apache.org/install.html) (if using Maven) +Install [Gradle](https://gradle.org/install) (if using Gradle) ### Dependencies -One of the goals of the reference implementation is to maintain the number of dependencies as lower as possible. With that spirit, a modular approach has been followed, letting the users decide, depending on their workflows nature, which dependencies should be include. - -In practical terms, this means a separation between the core part and additional dependencies that should be explicitly included if your workflow is interacting with an external service that communicated using a particular technology supported by the specification (at this moment, just HTTP). The intention of this is to avoid adding dependencies that you do not really need (for example, when gRPC call will be implemented, if we were adding the gRPC stack to the core dependencies, you wont be able to get rid of it even if none of your workflows use it) +This implementation follows a modular approach, keeping dependencies minimal: +- The core library is always required. +- Additional dependencies must be explicitly included if your workflow interacts with external services (e.g., HTTP). +This ensures you only include what you need, preventing unnecessary dependencies. #### Maven @@ -58,7 +59,7 @@ You always need to add this dependency to your pom.xml `dependencies` section: io.serverlessworkflow serverlessworkflow-impl-core - 7.0.0-SNAPSHOT + 7.0.0 ``` @@ -68,41 +69,38 @@ And only if your workflow is using HTTP calls, you must add: io.serverlessworkflow serverlessworkflow-impl-http - 7.0.0-SNAPSHOT + 7.0.0 ``` - - -### Gradle projects: +#### Gradle projects: You always need to add this dependency to your build.gradle `dependencies` section: ```text -implementation("io.serverlessworkflow:serverlessworkflow-impl-core:7.0.0-SNAPSHOT") +implementation("io.serverlessworkflow:serverlessworkflow-impl-core:7.0.0") ``` And only if your workflow is using HTTP calls, you must add: ```text -implementation("io.serverlessworkflow:serverlessworkflow-impl-http:7.0.0-SNAPSHOT") +implementation("io.serverlessworkflow:serverlessworkflow-impl-http:7.0.0") ``` - ## How to use -Quick version is intended for impatient users that want to try something as soon as possible. +The quick version is intended for impatient users who want to try something as soon as possible. -Detailed version is more suitable for those users interested on a more thoughtful discussion of the API. +The detailed version is more suitable for those users interested in a more thoughtful discussion of the API. ### Quick version -For a quick introduction, we are going to use a simple workflow [definition](../examples/simpleGet/src/main/resources/get.yaml) that performs a get call. +For a quick introduction, we will use a simple workflow [definition](../examples/simpleGet/src/main/resources/get.yaml) that performs a get call. We are going to show two ways of invoking the workflow: - blocking the thread till the get request goes through - returning control to the caller, so the main thread continues while the get is executed -In order to execute the workflow, blocking the thread till the http request is completed, you should write +In order to execute the workflow, blocking the thread till the HTTP request is completed, you should write ``` java try (WorkflowApplication appl = WorkflowApplication.builder().build()) { @@ -116,7 +114,7 @@ try (WorkflowApplication appl = WorkflowApplication.builder().build()) { ``` You can find the complete java code [here](../examples/simpleGet/src/main/java/BlockingExample.java) -In order to execute the workflow, without blocking the calling thread till the http request is completed, you should write +In order to execute the workflow without blocking the calling thread till the HTTP request is completed, you should write ``` java try (WorkflowApplication appl = WorkflowApplication.builder().build()) { @@ -126,9 +124,12 @@ In order to execute the workflow, without blocking the calling thread till the h .thenAccept(node -> logger.info("Workflow output is {}", node)); } ``` -When the http request is done, both examples will print a similar output +When the HTTP request is done, both examples will print a similar output + -`Workflow output is {"id":10,"category":{"id":10,"name":"string"},"name":"doggie","photoUrls":["string"],"tags":[{"id":10,"name":"string"}],"status":"string"}` +```shell +Workflow output is {"id":10,"category":{"id":10,"name":"string"},"name":"doggie","photoUrls":["string"],"tags":[{"id":10,"name":"string"}],"status":"string"} +``` You can find the complete java code [here](../examples/simpleGet/src/main/java/NotBlockingExample.java) @@ -138,15 +139,19 @@ To discuss runtime API we are going to use a couple of workflow: - [listen.yaml](../examples/events/src/main/listen.yaml), which waits for an event reporting a temperature greater than 38 - [emit.yaml](../examples/events/src/main/emit.yaml), which emits events with a certain temperature, specified as workflow parameter. -A brief summary of what we are trying to do. We will start listen.yaml, which will complete when it receives an event with the proper temperature, but it wont block the main thread while waiting for it. Then, we will send an event with a lower temperature, that will be ignored. And finally, we will send an event with a greater temperature, that will complete the waiting workflow. +Here is a summary of what we are trying to do: + +- The listen.yaml workflow waits for an event (not-blocking). +- We send an event with a low temperature (ignored). +- We send an event with a high temperature (completes the workflow). -The first step is to create a [WorkflowApplication](core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java) instance. An application is an abstraction that allow customization of different aspect of the workflow execution (for example change the default `ExecutorService` for thread spawning) +The first step is to create a [WorkflowApplication](core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java) instance. An application is an abstraction that allows customization of different aspects of the workflow execution (for example, change the default `ExecutorService` for thread spawning) -Since `WorkflowApplication` implements `Autocloseable`, we better use a try...finally block, ensuring any resource that might have been used by the workflow is freed when done. +Since `WorkflowApplication` implements `Autocloseable`, we better use a **try-with-resources** block, ensuring any resource that the workflow might have used is freed when done. `try (WorkflowApplication appl = WorkflowApplication.builder().build())` -Once we have the application object, we use it to parse our definition examples. To load each workflow definition, we use `readFromClasspath` helper method defined in [WorkflowReader](api/src/main/java/io/serverlessworkflow/api/WorkflowReader.java) class. +Once we have the application object, we use it to parse our definition examples. To load each workflow definition, we use the `readFromClasspath` helper method defined in [WorkflowReader](api/src/main/java/io/serverlessworkflow/api/WorkflowReader.java) class. ```java WorkflowDefinition listenDefinition = @@ -155,9 +160,9 @@ Once we have the application object, we use it to parse our definition examples. appl.workflowDefinition(WorkflowReader.readWorkflowFromClasspath("emit.yaml")); ``` -A [WorkflowDefinition](core/src/main/java/io/serverlessworkflow/impl/WorkflowDefinition.java) object is immutable and therefore thread safe. It is used to execute as many workflow instances as desired. +A [WorkflowDefinition](core/src/main/java/io/serverlessworkflow/impl/WorkflowDefinition.java) object is immutable and, therefore, thread-safe. It is used to execute as many workflow instances as desired. -To execute a workflow, we first create a [WorkflowInstance](core/src/main/java/io/serverlessworkflow/impl/WorkflowInstance.java) object (the initial status is PENDING) and then invoke `start` method on it (the status is changed to RUNNING). `start` method returns a [CompletableFuture](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html), which we use to indicate that a log message should be printed when the workflow is completed. +To execute a workflow, we first create a [WorkflowInstance](core/src/main/java/io/serverlessworkflow/impl/WorkflowInstance.java) object (its initial status is PENDING) and then invoke the `start` method on it (its status is changed to RUNNING). The `start` method returns a [CompletableFuture](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html), which we use to indicate that a log message should be printed when the workflow is completed. ```java WorkflowInstance waitingInstance = listenDefinition.instance(Map.of()); @@ -166,15 +171,15 @@ To execute a workflow, we first create a [WorkflowInstance](core/src/main/java/i .thenAccept(node -> logger.info("Waiting instance completed with result {}", node)); ``` -The next line will be executed as soon as the workflow execution starts waiting for events to arrive, moment at which control is returned to the calling thread. Therefore, we can execute another workflow instance while the first one is waiting. +As soon as the workflow execution reach the point where it waits for events to arrive, control is returned to the calling thread. Since the execution is not blocking, we can execute another workflow instance while the first one is waiting. -We are going to send an event with a temperature that does not satisfy the criteria, so the listen instance will continue waiting. To pass parameters to the workflow instance that sends the event, we use a regular Java `Map`. Notice that, since we want to wait till the event is published before executing the next line, we call `join` after `start`, telling the `CompletableFuture` to wait for workflow completion. +We will send an event with a temperature that does not satisfy the criteria, so the listen instance will continue waiting. We use a regular Java `Map` to pass parameters to the workflow instance that sends the event. Note that since we want to wait till the event is published, we call `join` after `start`, telling the `CompletableFuture` to wait for workflow completion. ```java emitDefinition.instance(Map.of("temperature", 35)).start().join(); ``` - Now its time to complete the waiting instance and send an event with the expected temperature. We do so by reusing `emitDefinition`. + It's time to complete the waiting instance and send an event with the expected temperature. We do so by reusing `emitDefinition`. ```java emitDefinition.instance(Map.of("temperature", 39)).start().join(); @@ -185,5 +190,5 @@ After that, listen instance will be completed and we will see this log message ```java [pool-1-thread-1] INFO events.EventExample - Waiting instance completed with result [{"temperature":39}] ``` -The source code of the example is here (../examples/events/src/main/java/EventExample.java) +The source code of the example is [here](../examples/events/src/main/java/EventExample.java) From 8f4ec431bf101155ae2c02d8ad9a98c2f00938bd Mon Sep 17 00:00:00 2001 From: Francisco Javier Tirado Sarti Date: Fri, 31 Jan 2025 12:29:00 +0100 Subject: [PATCH 3/3] [Fix #520] Refining before release Signed-off-by: Francisco Javier Tirado Sarti --- .../main/java/io/serverlessworkflow/impl/WorkflowInstance.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowInstance.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowInstance.java index 83cb9030..2e55c484 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowInstance.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowInstance.java @@ -68,7 +68,7 @@ private JsonNode whenCompleted(JsonNode node) { .map(f -> f.apply(workflowContext, null, node)) .orElse(node); workflowContext.definition().outputSchemaValidator().ifPresent(v -> v.validate(output)); - status.set(WorkflowStatus.COMPLETED); + status.compareAndSet(WorkflowStatus.RUNNING, WorkflowStatus.COMPLETED); completedAt = Instant.now(); return output; }