adding management example

Signed-off-by: salaboy <Salaboy@gmail.com>
This commit is contained in:
salaboy 2025-07-11 11:14:40 +01:00
parent 4e57252e24
commit 55217f8f73
14 changed files with 548 additions and 16 deletions

View File

@ -1,24 +1,10 @@
@apphost=http://localhost:8080
### Start the TaskChaining workflow
# @name startWorkflowRequest
POST {{ apphost }}/start
### Get the workflow status
GET {{ apphost }}/output
### DELETE
@apphost=http://localhost:5257
### Start the Monitor workflow
# @name startWorkflowRequest
@counter=0
POST {{ apphost }}/start/{{counter}}
### Get the workflow status
@instanceId={{startWorkflowRequest.response.headers.Location}}
@daprHost=http://localhost:3557
GET {{ daprHost }}/v1.0/workflows/dapr/{{ instanceId }}
GET {{ apphost }}/output

View File

@ -0,0 +1,33 @@
# Workflow Management
This tutorial demonstrates the various APIs to manage a workflow instance, these methods include:
- Scheduling a workflow instance
- Getting the workflow instance state
- Suspending the workflow instance
- Resuming the workflow instance
- Terminating the workflow instance
- Purging the workflow instance
For more information on workflow management, see the [Dapr docs](https://docs.dapr.io/developing-applications/building-blocks/workflow/howto-manage-workflow/).
## Inspect the code
Open the `Program.cs` file in the `tutorials/workflow/csharp/workflow-management/WorkflowManagement` folder. This file contains the endpoint definitions that use the workflow management API. The workflow that is being managed is named `NeverEndingWorkflow` and is a counter that will keep running once it's started.
## Run the tutorial
1. Use a terminal to navigate to the `tutorials/workflow/java/workflow-management` folder.
2. Build and run the project using Maven.
```bash
mvn spring-boot:test-run
```
3. Use the first POST request in the [`workflowmanagement.http`](./workflowmanagement.http) file to start the workflow.
4. Use other requests in the [`workflowmanagement.http`](./workflowmanagement.http) file to perform other actions on the workflow, such as:
- Getting the workflow instance state.
- Suspending & resuming the workflow instance.
- Terminating the workflow instance.
- Purging the workflow instance.
5. Stop the application by pressing `Ctrl+C`.

View File

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<artifactId>workflow-management</artifactId>
<name>workflow-management</name>
<description>Workflow Management Example</description>
<properties>
<dapr.spring.version>0.15.0-rc-7</dapr.spring.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>io.dapr.spring</groupId>
<artifactId>dapr-spring-boot-starter</artifactId>
<version>${dapr.spring.version}</version>
</dependency>
<dependency>
<groupId>io.dapr.spring</groupId>
<artifactId>dapr-spring-boot-starter-test</artifactId>
<version>${dapr.spring.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,27 @@
/*
* Copyright 2025 The Dapr Authors
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
limitations under the License.
*/
package io.dapr.springboot.examples;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class WorkflowManagementApplication {
public static void main(String[] args) {
SpringApplication.run(WorkflowManagementApplication.class, args);
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright 2025 The Dapr Authors
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
limitations under the License.
*/
package io.dapr.springboot.examples;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class WorkflowManagementConfiguration {
@Bean
public RestTemplate restTemplate() {
return new RestTemplateBuilder().build();
}
@Bean
public ObjectMapper mapper() {
return new ObjectMapper();
}
}

View File

@ -0,0 +1,96 @@
/*
* Copyright 2025 The Dapr Authors
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
limitations under the License.
*/
package io.dapr.springboot.examples;
import io.dapr.spring.workflows.config.EnableDaprWorkflows;
import io.dapr.springboot.examples.mgmt.NeverEndingWorkflow;
import io.dapr.workflows.client.DaprWorkflowClient;
import io.dapr.workflows.client.WorkflowInstanceStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.concurrent.TimeoutException;
@RestController
@EnableDaprWorkflows
public class WorkflowManagementRestController {
private final Logger logger = LoggerFactory.getLogger(WorkflowManagementRestController.class);
@Autowired
private DaprWorkflowClient daprWorkflowClient;
/**
* Run NeverEnding Workflow
*
* @return the instanceId of the NeverEndingWorkflow execution
*/
@PostMapping("start/{counter}")
public String start(@PathVariable("counter") Integer counter) throws TimeoutException {
return daprWorkflowClient.scheduleNewWorkflow(NeverEndingWorkflow.class, counter);
}
/**
* Obtain the status of the workflow
*
* @return the output of the NeverEndingWorkflow execution
*/
@GetMapping("status/{instanceId}")
public String status(@PathVariable("instanceId") String instanceId) {
WorkflowInstanceStatus instanceState = daprWorkflowClient.getInstanceState(instanceId, true);
if (instanceState != null) {
return instanceState.toString();
}
return "N/A";
}
/**
* Suspend Workflow Instance
*
*/
@PostMapping("suspend/{instanceId}")
public void suspend(@PathVariable("instanceId") String instanceId) {
daprWorkflowClient.suspendWorkflow(instanceId, "");
}
/**
* Resume Workflow Instance
*/
@PostMapping("resume/{instanceId}")
public void resume(@PathVariable("instanceId") String instanceId) {
daprWorkflowClient.resumeWorkflow(instanceId, "");
}
/**
* Terminate Workflow Instance
*/
@PostMapping("terminate/{instanceId}")
public void terminate(@PathVariable("instanceId") String instanceId) {
daprWorkflowClient.terminateWorkflow(instanceId, null);
}
/**
* Purge Workflow Instance
*/
@DeleteMapping("purge/{instanceId}")
public boolean purge(@PathVariable("instanceId") String instanceId) {
return daprWorkflowClient.purgeInstance(instanceId);
}
}

View File

@ -0,0 +1,39 @@
/*
* 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.springboot.examples.mgmt;
import io.dapr.workflows.Workflow;
import io.dapr.workflows.WorkflowStub;
import org.springframework.stereotype.Component;
import java.time.Duration;
@Component
public class NeverEndingWorkflow implements Workflow {
@Override
public WorkflowStub create() {
return ctx -> {
var counter = ctx.getInput(Integer.class);
String result = ctx.callActivity(SendNotificationActivity.class.getName(), counter, String.class).await();
ctx.createTimer(Duration.ofSeconds(1)).await();
counter++;
ctx.continueAsNew(counter);
ctx.complete(true);
};
}
}

View File

@ -0,0 +1,40 @@
/*
* 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.springboot.examples.mgmt;
import io.dapr.workflows.WorkflowActivity;
import io.dapr.workflows.WorkflowActivityContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/*
*/
@Component
public class SendNotificationActivity implements WorkflowActivity {
/*
*/
@Override
public Object run(WorkflowActivityContext ctx) {
Logger logger = LoggerFactory.getLogger(SendNotificationActivity.class);
var message = ctx.getInput(String.class);
// Imagine a notification being sent to the user
logger.info("{} : Sending Notification: {}", ctx.getName(), message);
return true;
}
}

View File

@ -0,0 +1 @@
spring.application.name=workflow-management

View File

@ -0,0 +1,43 @@
/*
* Copyright 2025 The Dapr Authors
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
limitations under the License.
*/
package io.dapr.springboot.examples;
import io.dapr.testcontainers.Component;
import io.dapr.testcontainers.DaprContainer;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.springframework.context.annotation.Bean;
import java.util.Collections;
import static io.dapr.testcontainers.DaprContainerConstants.DAPR_RUNTIME_IMAGE_TAG;
@TestConfiguration(proxyBeanMethods = false)
public class DaprTestContainersConfig {
@Bean
@ServiceConnection
public DaprContainer daprContainer() {
return new DaprContainer(DAPR_RUNTIME_IMAGE_TAG)
.withAppName("workflow-patterns-app")
.withComponent(new Component("kvstore", "state.in-memory", "v1", Collections.singletonMap("actorStateStore", String.valueOf(true))))
.withAppPort(8080)
.withAppHealthCheckPath("/actuator/health")
.withAppChannelAddress("host.testcontainers.internal");
}
}

View File

@ -0,0 +1,31 @@
/*
* Copyright 2025 The Dapr Authors
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
limitations under the License.
*/
package io.dapr.springboot.examples;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class TestWorkflowManagementApplication {
public static void main(String[] args) {
SpringApplication.from(WorkflowManagementApplication::main)
.with(DaprTestContainersConfig.class)
.run(args);
org.testcontainers.Testcontainers.exposeHostPorts(8080);
}
}

View File

@ -0,0 +1,121 @@
/*
* Copyright 2025 The Dapr Authors
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
limitations under the License.
*/
package io.dapr.springboot.examples;
import io.dapr.client.DaprClient;
import io.dapr.springboot.DaprAutoConfiguration;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import static io.restassured.RestAssured.given;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest(classes = {TestWorkflowManagementApplication.class, DaprTestContainersConfig.class,
DaprAutoConfiguration.class, },
webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
class WorkflowManagementAppTests {
@Autowired
private DaprClient daprClient;
@BeforeEach
void setUp() {
RestAssured.baseURI = "http://localhost:" + 8080;
org.testcontainers.Testcontainers.exposeHostPorts(8080);
}
@Test
void testWorkflowManagement() {
String workflowId = given().contentType(ContentType.JSON)
.pathParam("counter", 0)
.when()
.post("/start/{counter}")
.then()
.statusCode(200)
.extract().asString();
assertNotNull(workflowId);
String status = given().contentType(ContentType.JSON)
.pathParam("instanceId", workflowId)
.when()
.get("/status/{instanceId}")
.then()
.statusCode(200).extract().asString();
assertTrue(status.contains("RUNNING"));
given().contentType(ContentType.JSON)
.pathParam("instanceId", workflowId)
.when()
.post("/suspend/{instanceId}")
.then()
.statusCode(200);
status = given().contentType(ContentType.JSON)
.pathParam("instanceId", workflowId)
.when()
.get("/status/{instanceId}")
.then()
.statusCode(200).extract().asString();
assertTrue(status.contains("SUSPENDED"));
given().contentType(ContentType.JSON)
.pathParam("instanceId", workflowId)
.when()
.post("/resume/{instanceId}")
.then()
.statusCode(200);
status = given().contentType(ContentType.JSON)
.pathParam("instanceId", workflowId)
.when()
.get("/status/{instanceId}")
.then()
.statusCode(200).extract().asString();
assertTrue(status.contains("RUNNING"));
given().contentType(ContentType.JSON)
.pathParam("instanceId", workflowId)
.when()
.post("/terminate/{instanceId}")
.then()
.statusCode(200);
status = given().contentType(ContentType.JSON)
.pathParam("instanceId", workflowId)
.when()
.get("/status/{instanceId}")
.then()
.statusCode(200).extract().asString();
assertTrue(status.contains("TERMINATED"));
}
}

View File

@ -0,0 +1 @@
spring.application.name=workflow-management

View File

@ -0,0 +1,21 @@
@apphost=http://localhost:8080
### Start the NeverEndingWorkflow workflow
# @name startWorkflowRequest
@counter=0
POST {{ apphost }}/start/{{counter}}
### Get the workflow status via the application
GET {{ apphost }}/status/{{ instanceId }}
### Suspend the workflow
POST {{ apphost }}/suspend/{{ instanceId }}
### Resume the workflow
POST {{ apphost }}/resume/{{ instanceId }}
### Terminate the workflow
POST {{ apphost }}/terminate/{{ instanceId }}
### Purge the workflow
DELETE {{ apphost }}/purge/{{ instanceId }}