Skip to main content

Dapr Workflow with Spring Boot

Build stateful, long-running, reliable workflows using the Dapr Workflow Spring Boot integration with Catalyst. The Dapr Workflow Spring Boot integration provides a powerful way to orchestrate complex business processes from within your Spring Boot applications.

Resources

Prerequisites:

Setting up

Maven

Add the following dependency to your pom.xml:

<dependency>
<groupId>io.dapr.spring</groupId>
<artifactId>dapr-spring-boot-starter</artifactId>
<version>1.xx.x</version>
</dependency>

Gradle

Add the following dependency to your build.gradle:

dependencies {
implementation 'io.dapr.spring:dapr-spring-boot-starter:1.xx.x'
}

If you don't have a Spring Boot application, you can create one by visiting the website: https://start.spring.io.

Start Spring IO

In the dependencies selector, select Spring Web and Spring Boot Actuator and click Generate.

This will download a Zip file that you need to unzip to start coding your Spring Boot application.

Open the pom.xml file and add the Dapr Spring Boot starter dependency as shown avobe.

Open with your favorite IDE and let's create your first Workflow.

Creating your first Workflow

Now that you have a Spring Boot application, let's create your first Workflow. To do this, you need to create a class that implements the Workflow interface.

package com.example.demo;

import io.dapr.workflows.Workflow;
import io.dapr.workflows.WorkflowStub;
import org.springframework.stereotype.Component;

@Component
public class SimpleWorkflow implements Workflow {

@Override
public WorkflowStub create() {
return ctx -> {
String instanceId = ctx.getInstanceId();

// <YOUR WORKFLOW LOGIC GOES HERE>

ctx.complete("Workflow completed");
};
}
}

You can have as many workflows as you want inside your Spring Boot projects.

Notice: Use the Spring @Component to make sure that the workflow definition is a managed Spring bean.

Once you have your Workflow class, the next step is to implement your workflow logic. This can include:

  • Calling activities: by using the ctx.callActivity(...) method we can build durable integrations with other systems.
  • Waiting for external events: by using the ctx.waitForExternalEvent(...) method the workflow will wait for events coming from outside your application.
  • Creating timers: the application can schedule timers to resume the workflow execution at a specific point in time in the future.
  • Making logical choices: using Java constructs (if/switch statements) you can define multiple workflow branches and their behavior.
  • Calling Child Workflows: compose complex scenarios by creating parent/child relationships across workflow definitions.

Let's expand the workflow definition to call the first Activity.

  @Override
public WorkflowStub create() {
return ctx -> {
String instanceId = ctx.getInstanceId();
ctx.getLogger().info("> Workflow started {}", instanceId);
ctx.callActivity(FirstActivity.class.getName()).await();

// <MORE WORKFLOW LOGIC GOES HERE>

ctx.complete("Workflow completed");
};
}

Two lines are added to the Workflow definition. The first one is using ctx.getLogger().info() to log the workflow instance id. This is useful to troubleshoot workflow instance executions. The next line calls a new activity called FirstActivity.

Workflow Activities give you the perfect place to call other services to build complex workflow orchestrations.

But this activity is not defined yet, so let's go ahead and do that:

package com.example.demo;

import io.dapr.workflows.WorkflowActivity;
import io.dapr.workflows.WorkflowActivityContext;
import org.slf4j.Logger;
import org.springframework.stereotype.Component;

@Component
public class FirstActivity implements WorkflowActivity {
private Logger logger = org.slf4j.LoggerFactory.getLogger(FirstActivity.class);

@Override
public Object run(WorkflowActivityContext ctx) {
logger.info("Executing the First activity.");
return null;
}
}

You can create new workflow activities by implementing the WorkflowActivity interface.

Inside the Object run(WorkflowActivityContext ctx) you can implement business logic to integrate with other systems. Activities will be called by workflow definitions and can be reused by multiple workflows.

At this point you have a fully functional workflow that you can run, but you still need to wire some things up to make sure that you can start new workflow instances from the Spring Boot Application.

For workflows to be discovered and registered to the workflow runtime, you need to add the @EnableDaprWorkflow annotation to the Spring Boot application class.

package com.example.demo;

import io.dapr.spring.workflows.config.EnableDaprWorkflows;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@EnableDaprWorkflows
public class DemoApplication {

public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}

}

When the application starts, the @EnableDaprWorkflows annotation scans the classpath to look for Workflows and WorkflowActivitys and automatically register them.

Finally, you need to be able to start workflow instances from the Spring Boot application. One way to do this, is to create a @RestController that you can invoke from a user interface or another application.


package com.example.demo;


import io.dapr.workflows.client.DaprWorkflowClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class DemoRestController {

@Autowired
private DaprWorkflowClient daprWorklfowClient;

@PostMapping("/start")
public String start() {
return daprWorklfowClient.scheduleNewWorkflow(SimpleWorkflow.class);
}

}

This is a minimal implementation with a single REST endpoint. When a POST request is sent to this endpoint it will schedule a new workflow instance.

As you can see, the DaprWorkflowClient is injected (@Autowired) to be able to use the scheduleNewWorkflow() method to start new instances of the SimpleWorkflow.

Now you have a complete workflow application that you can run locally.

Local development mode with Testcontainers and Diagrid's Workflow Dashboard

To run Dapr Workflow applications in production, you need to install Dapr into a Kubernetes cluster. To speed up the inner development loop, you can run everything locally using the Dapr and Testcontainers integration.

To run in development mode, use Spring Boot test-run goal and the Testcontainers integration, that starts the Dapr containers and automatically connect the application.

To use this functionality you need to add the follow dependency to the application, using test scope.

Maven

Add the following dependency to pom.xml:

<dependency>
<groupId>io.dapr.spring</groupId>
<artifactId>dapr-spring-boot-starter-test</artifactId>
<version>1.xx.x</version>
<scope>test</scope>
</dependency>

Gradle

Add the following dependency to build.gradle:

dependencies {
test 'io.dapr.spring:dapr-spring-boot-starter-test:1.xx.x'
}

Once you have this test dependency, you can use the Dapr Testcontainers integration to start Dapr right besides your Spring Boot application for development purposes. The only change you need inside your application test source is to have a @Testconfiguration with the DaprContainer.

  @Bean
@ServiceConnection
DaprContainer daprContainer(Network network, GenericContainer<?> otelCollectorContainer) {


return new DaprContainer(daprImage)
.withNetwork(network)
.withAppName("workflow-app-dapr")
.withAppPort(8080)
.withComponent(new Component("kvstore", "state.in-memory", "v1",
Map.of("actorStateStore", "true" )))
.withAppChannelAddress("host.testcontainers.internal")
.withAppHealthCheckPath("/actuator/health")
.dependsOn(otelCollectorContainer);
}

With this test configuration, whenever you start your Spring Boot application, Dapr will start automatically for local development.

Creating new workflow instances

Now, that the application is up and running you, can start a new workflow instance by calling the /start endpoint.

Let's use cURL to send a POST request to /start

curl -X POST localhost:8080/start

Every time we send a POST request a new workflow instance will be started.

Running the application against Diagrid's Catalyst SaaS

Once you have written your workflows and tested them locally you run against the Diagrid's Catalyst SaaS service that runs remotely on the cloud.

To do this, you need to download the Diagrid CLI and use it in conjunction with Maven to start your application.

To run the application locally against Catalyst, use the diagrid dev run command in conjunction with mvn spring-boot:run. This will start the Spring Boot application and connect to the Catalyst Workflow Engine to run your workflows.

diagrid dev run --project spring-boot --app-id workflows --app-port 8080 -- mvn spring-boot:run

The first part of the command will create the spring-boot project in Catalyst along with the workflows app-id. Once this is done, Maven will take care of starting our Spring Boot application.

You can check your Workflow executions using the Diagrid's Catalyst workflow visualizer.