/sdk-java

Java SDK for Serverless Workflow

Primary LanguageJavaApache License 2.0Apache-2.0

Verify JAVA SDK Deploy JAVA SDK Gitpod ready-to-code

Serverless Workflow Specification - Java SDK

Provides the Java API/SPI and Model Validation for the Serverless Workflow Specification

With the SDK you can:

  • Parse workflow JSON and YAML definitions
  • Programmatically build workflow definitions
  • Validate workflow definitions (both schema and workflow integrity validation)
  • Generate workflow diagram (SVG)
  • Set of utilities to help runtimes interpret the Serverless Workflow object model

Serverless Workflow Java SDK is not a workflow runtime implementation but can be used by Java runtime implementations to parse and validate workflow definitions as well as generate the workflow diagram (SVG).

Status

Latest Releases Conformance to spec version
4.0.4.Final v0.8
4.0.3.Final v0.8
3.0.0.Final v0.7
2.0.0.Final v0.6
1.0.3.Final v0.5

JDK Version

SDK Version JDK Version
5.0.0 and after 11
4.0.x and before 8

Getting Started

Using the latest release

See instructions how to define dependencies for the latest SDK release for both Maven and Gradle here.

Building SNAPSHOT locally

To build project and run tests locally:

git clone https://github.com/serverlessworkflow/sdk-java.git
mvn clean install

The project uses Google's code styleguide. Your changes should be automatically formatted during the build.

Maven projects:

a) Add the following repository to your pom.xml repositories section:

<repository>
    <id>oss.sonatype.org-snapshot</id>
    <url>http://oss.sonatype.org/content/repositories/snapshots</url>
    <releases>
        <enabled>false</enabled>
    </releases>
    <snapshots>
        <enabled>true</enabled>
    </snapshots>
</repository>

b) Add the following dependencies to your pom.xml dependencies section:

<dependency>
    <groupId>io.serverlessworkflow</groupId>
    <artifactId>serverlessworkflow-api</artifactId>
    <version>5.0.0-SNAPSHOT</version>
</dependency>

<dependency>
    <groupId>io.serverlessworkflow</groupId>
    <artifactId>serverlessworkflow-spi</artifactId>
    <version>5.0.0-SNAPSHOT</version>
</dependency>

<dependency>
    <groupId>io.serverlessworkflow</groupId>
    <artifactId>serverlessworkflow-validation</artifactId>
    <version>5.0.0-SNAPSHOT</version>
</dependency>

<dependency>
    <groupId>io.serverlessworkflow</groupId>
    <artifactId>serverlessworkflow-diagram</artifactId>
    <version>5.0.0-SNAPSHOT</version>
</dependency>

<dependency>
    <groupId>io.serverlessworkflow</groupId>
    <artifactId>serverlessworkflow-util</artifactId>
    <version>5.0.0-SNAPSHOT</version>
</dependency>

Gradle projects:

a) Add the following repositories to your build.gradle repositories section:

maven { url "https://oss.sonatype.org/content/repositories/snapshots" }

b) Add the following dependencies to your build.gradle dependencies section:

implementation("io.serverlessworkflow:serverlessworkflow-api:5.0.0-SNAPSHOT")
implementation("io.serverlessworkflow:serverlessworkflow-spi:5.0.0-SNAPSHOT")
implementation("io.serverlessworkflow:serverlessworkflow-validation:5.0.0-SNAPSHOT")
implementation("io.serverlessworkflow:serverlessworkflow-diagram:5.0.0-SNAPSHOT")
implementation("io.serverlessworkflow:serverlessworkflow-util:5.0.0-SNAPSHOT")

How to Use

Creating from JSON/YAML source

You can create a Workflow instance from JSON/YAML source:

Let's say you have a simple YAML based workflow definition:

id: greeting
version: '1.0'
name: Greeting Workflow
start: Greet
description: Greet Someone
functions:
  - name: greetingFunction
    operation: file://myapis/greetingapis.json#greeting
states:
- name: Greet
  type: operation
  actions:
  - functionRef:
      refName: greetingFunction
      arguments:
        name: "${ .greet.name }"
    actionDataFilter:
      results: "${ .payload.greeting }"
  stateDataFilter:
    output: "${ .greeting }"
  end: true

To parse it and create a Workflow instance you can do:

Workflow workflow = Workflow.fromSource(source);

where 'source' is the above mentioned YAML definition.

The fromSource static method can take in definitions in both JSON and YAML formats.

Once you have the Workflow instance you can use its API to inspect it, for example:

assertNotNull(workflow);
assertEquals("greeting", workflow.getId());
assertEquals("Greeting Workflow", workflow.getName());

assertNotNull(workflow.getFunctions());
assertEquals(1, workflow.getFunctions().size());
assertEquals("greetingFunction", workflow.getFunctions().get(0).getName());

assertNotNull(workflow.getStates());
assertEquals(1, workflow.getStates().size());
assertTrue(workflow.getStates().get(0) instanceof OperationState);

OperationState operationState = (OperationState) workflow.getStates().get(0);
assertEquals("Greet", operationState.getName());
assertEquals(DefaultState.Type.OPERATION, operationState.getType());
...

Using builder API

You can also programmatically create Workflow instances, for example:

Workflow workflow = new Workflow()
                .withId("test-workflow")
                .withName("test-workflow-name")
                .withVersion("1.0")
                .withStart(new Start().withStateName("MyDelayState"))
                .withFunctions(new Functions(Arrays.asList(
                        new FunctionDefinition().withName("testFunction")
                                .withOperation("testSwaggerDef#testOperationId")))
                )
                .withStates(Arrays.asList(
                        new DelayState().withName("MyDelayState").withType(DELAY)
                                .withTimeDelay("PT1M")
                                .withEnd(
                                        new End().withTerminate(true)
                                )
                        )
                );

This will create a test workflow that defines an event, a function and a single Delay State.

You can use the workflow instance to get its JSON/YAML definition as well:

assertNotNull(Workflow.toJson(testWorkflow));
assertNotNull(Workflow.toYaml(testWorkflow));

Using Workflow Validation

Validation allows you to perform Json Schema validation against the JSON/YAML workflow definitions. Once you have a Workflow instance, you can also run integrity checks.

You can validate a Workflow JSON/YAML definition to get validation errors:

WorkflowValidator workflowValidator = new WorkflowValidatorImpl();
List<ValidationError> validationErrors = workflowValidator.setSource("WORKFLOW_MODEL_JSON/YAML").validate();

Where WORKFLOW_MODEL_JSON/YAML is the actual workflow model JSON or YAML definition.

Or you can just check if it is valid (without getting specific errors):

WorkflowValidator workflowValidator = new WorkflowValidatorImpl();
boolean isValidWorkflow = workflowValidator.setSource("WORKFLOW_MODEL_JSON/YAML").isValid();

If you build your Workflow programmatically, you can validate it as well:

Workflow workflow = new Workflow()
                .withId("test-workflow")
                .withVersion("1.0")
                .withStart(new Start().withStateName("MyDelayState"))
                .withStates(Arrays.asList(
                        new DelayState().withName("MyDelayState").withType(DefaultState.Type.DELAY)
                                .withTimeDelay("PT1M")
                                .withEnd(
                                        new End().withTerminate(true)
                                )
                ));
);

WorkflowValidator workflowValidator = new WorkflowValidatorImpl();
List<ValidationError> validationErrors = workflowValidator.setWorkflow(workflow).validate();

Building Workflow Diagram

Given a valid workflow definition (JSON/YAML) or a Workflow object you can build the workflow diagram SVG. The generated diagram SVG uses PlantUML state diagram visualization and can be embedded inside your tooling or web pages, or any SVG viewer.

You can build the workflow diagram SVG with the following code:

Workflow workflow = Workflow.fromSource(source);

WorkflowDiagram workflowDiagram = new WorkflowDiagramImpl();
workflowDiagram.setWorkflow(workflow);

String diagramSVG = workflowDiagram.getSvgDiagram();

diagramSVG includes the diagram SVG source which you can then decide to save to a file, print, or process further.

In case default visualization of the workflow is not sufficient you can provide custom workflow template to be used while generating the SVG file. Easiest is to start off from the default template and customize it to your needs.

Custom template must be on the classpath in templates/plantuml directory and must use .txt extension. Next template is set on WorkflowDiagram instance as shown below.

Workflow workflow = Workflow.fromSource(source);

WorkflowDiagram workflowDiagram = new WorkflowDiagramImpl();
workflowDiagram.setWorkflow(workflow);
workflowDiagram.setTemplate("custom-template");

String diagramSVG = workflowDiagram.getSvgDiagram();

By default the diagram legend is now shown. If you want to enable it you can do:

Workflow workflow = Workflow.fromSource(source);

WorkflowDiagram workflowDiagram = new WorkflowDiagramImpl();
workflowDiagram.setWorkflow(workflow)
               .showLegend(true);

String diagramSVG = workflowDiagram.getSvgDiagram();

Here are some generated diagrams from the specification examples (with legend enabled):

  1. Job Monitoring Example

Job Monitoring Example Diagram

  1. Send CloudEvent on Workflow completion Example

Send Cloud Event on Workflow completion

Using Workflow Utils

Workflow utils provide a number of useful methods for extracting information from workflow definitions. Once you have a Workflow instance, you can use it

Get Starting State
State startingState = WorkflowUtils.getStartingState(workflow);
Get States by State Type
    List<State> states = WorkflowUtils.getStates(workflow, DefaultState.Type.EVENT);
Get Consumed-Events, Produced-Events and their count
 List<EventDefinition> consumedEvents = WorkflowUtils.getWorkflowConsumedEvents(workflow);
 int consumedEventsCount = WorkflowUtils.getWorkflowConsumedEventsCount(workflow);

 List<EventDefinition> producedEvents = WorkflowUtils.getWorkflowProducedEvents(workflow);
 int producedEventsCount = WorkflowUtils.getWorkflowProducedEventsCount(workflow);
Get Defined Consumed-Events, Defined Produced-Events and their count
 List<EventDefinition> consumedEvents = WorkflowUtils.getWorkflowConsumedEventsCount(workflow);
 int consumedEventsCount = WorkflowUtils.getWorkflowConsumedEventsCount(workflow);

 List<EventDefinition> producedEvents = WorkflowUtils.getWorkflowProducedEvents(workflow);
 int producedEventsCount = WorkflowUtils.getWorkflowProducedEventsCount(workflow);
Get Function definitions which is used by an action
FunctionDefinition finalizeApplicationFunctionDefinition =
        WorkflowUtils.getFunctionDefinitionsForAction(workflow, "finalizeApplicationAction");
Get Actions which uses a Function definition
 List<Action> actionsForFunctionDefinition =
        WorkflowUtils.getActionsForFunctionDefinition(workflow, functionRefName);

Extra

SDK includes extra functionalities which are not part of core modules but are very useful and can be used as addons to the core:

Diagram REST

This is a Spring Boot app which builds a rest api for diagram generation. It was contributed by our community member David Marques.

To start using it:

cd diagram-rest
mvn clean install spring-boot:run

Then you can get the diagram SVG for a workflow definition for example:

curl -X POST localhost:8090/diagram -d '{"id":"booklending","name":"Book Lending Workflow","version":"1.0","specVersion":"0.8","start":"Book Lending Request","states":[{"name":"Book Lending Request","type":"event","onEvents":[{"eventRefs":["Book Lending Request Event"]}],"transition":"Get Book Status"},{"name":"Get Book Status","type":"operation","actions":[{"functionRef":{"refName":"Get status for book","arguments":{"bookid":"${ .book.id }"}}}],"transition":"Book Status Decision"},{"name":"Book Status Decision","type":"switch","dataConditions":[{"name":"Book is on loan","condition":"${ .book.status == \"onloan\" }","transition":"Report Status To Lender"},{"name":"Check is available","condition":"${ .book.status == \"available\" }","transition":"Check Out Book"}]},{"name":"Report Status To Lender","type":"operation","actions":[{"functionRef":{"refName":"Send status to lender","arguments":{"bookid":"${ .book.id }","message":"Book ${ .book.title } is already on loan"}}}],"transition":"Wait for Lender response"},{"name":"Wait for Lender response","type":"switch","eventConditions":[{"name":"Hold Book","eventRef":"Hold Book Event","transition":"Request Hold"},{"name":"Decline Book Hold","eventRef":"Decline Hold Event","transition":"Cancel Request"}]},{"name":"Request Hold","type":"operation","actions":[{"functionRef":{"refName":"Request hold for lender","arguments":{"bookid":"${ .book.id }","lender":"${ .lender }"}}}],"transition":"Wait two weeks"},{"name":"Wait two weeks","type":"sleep","duration":"P2W","transition":"Get Book Status"},{"name":"Check Out Book","type":"operation","actions":[{"functionRef":{"refName":"Check out book with id","arguments":{"bookid":"${ .book.id }"}}},{"functionRef":{"refName":"Notify Lender for checkout","arguments":{"bookid":"${ .book.id }","lender":"${ .lender }"}}}],"end":true}],"functions":[],"events":[]}'