Spring boot workflow patterns examples with mechanical markdown and tests (#1377)

* Feat Add TLS & mTLS support for gRPC with root CA and insecure mode (#1361)

* feat: Support for GRPC ssl

Signed-off-by: Javier Aliaga <javier@diagrid.io>

* add tests

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

* fix CI

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

* add back else if

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

* channel cleanup

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

* add root ca support

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

* checkstyles

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

* add insecure

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

* fix checkstyles

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

* use InsecureTrustManagerFactory

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

* fix test

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

---------

Signed-off-by: Javier Aliaga <javier@diagrid.io>
Signed-off-by: Cassandra Coyle <cassie@diagrid.io>
Co-authored-by: Javier Aliaga <javier@diagrid.io>
Signed-off-by: salaboy <Salaboy@gmail.com>

* spring boot workflow patterns initial version

Signed-off-by: salaboy <Salaboy@gmail.com>

* adding README for workflows

Signed-off-by: salaboy <Salaboy@gmail.com>

* adding child example

Signed-off-by: salaboy <Salaboy@gmail.com>

* updating examples to work with markdown tests

Signed-off-by: salaboy <Salaboy@gmail.com>

* running mechanical markdown for workflow examples

Signed-off-by: salaboy <Salaboy@gmail.com>

* Fix the issue with retries not happening correctly for Activities and Workflows (#1343)

* Add coverage for some properties (#1297)

Signed-off-by: sirivarma <siri.varma@outlook.com>

* Make the DAPR version being used consistent across all tests (#1299)

Signed-off-by: sirivarma <siri.varma@outlook.com>

* Separate Dapr constants from IT container constants (#1315)

Signed-off-by: Artur Ciocanu <ciocanu@adobe.com>
Co-authored-by: Artur Ciocanu <ciocanu@adobe.com>
Signed-off-by: sirivarma <siri.varma@outlook.com>

* Use Java Bean for connection details and add more tests (#1317)

* Use Java Bean for connection details and add more tests

Signed-off-by: Artur Ciocanu <ciocanu@adobe.com>

* Simplify mock setup

Signed-off-by: Artur Ciocanu <ciocanu@adobe.com>

* Adding even more tests for test coverage

Signed-off-by: Artur Ciocanu <ciocanu@adobe.com>

---------

Signed-off-by: Artur Ciocanu <ciocanu@adobe.com>
Co-authored-by: Artur Ciocanu <ciocanu@adobe.com>
Co-authored-by: Cassie Coyle <cassie@diagrid.io>
Signed-off-by: sirivarma <siri.varma@outlook.com>

* Update CONTRIBUTING.md

Signed-off-by: Siri Varma Vegiraju <siri.varma@outlook.com>
Signed-off-by: sirivarma <siri.varma@outlook.com>

* Bump codecov/codecov-action from 5.4.0 to 5.4.2 (#1318)

Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5.4.0 to 5.4.2.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v5.4.0...v5.4.2)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-version: 5.4.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Cassie Coyle <cassie@diagrid.io>
Co-authored-by: Dapr Bot <56698301+dapr-bot@users.noreply.github.com>
Signed-off-by: sirivarma <siri.varma@outlook.com>

* Fix URL building logic (#1320)

* Fix URL building logic

Signed-off-by: Artur Ciocanu <ciocanu@adobe.com>

* Add test for query params

Signed-off-by: Artur Ciocanu <ciocanu@adobe.com>

* Fix the assertion in the test

Signed-off-by: Artur Ciocanu <ciocanu@adobe.com>

* Adjust the tests

Signed-off-by: Artur Ciocanu <ciocanu@adobe.com>

* Remove uneeded changes from IT test

Signed-off-by: Artur Ciocanu <ciocanu@adobe.com>

* Revert some unintended changes

Signed-off-by: Artur Ciocanu <ciocanu@adobe.com>

* Simplify the testing a little bit

Signed-off-by: Artur Ciocanu <ciocanu@adobe.com>

* Adjust the test to use ServerRequest

Signed-off-by: Artur Ciocanu <ciocanu@adobe.com>

* Test removing things from method invoke controller

Signed-off-by: Artur Ciocanu <ciocanu@adobe.com>

* Add query param encoding test

Signed-off-by: Artur Ciocanu <ciocanu@adobe.com>

* Revert some unintended changes

Signed-off-by: Artur Ciocanu <ciocanu@adobe.com>

* Some tiny styles

Signed-off-by: Artur Ciocanu <ciocanu@adobe.com>

---------

Signed-off-by: Artur Ciocanu <ciocanu@adobe.com>
Co-authored-by: Artur Ciocanu <ciocanu@adobe.com>
Signed-off-by: sirivarma <siri.varma@outlook.com>

* Generate updated javadocs for 1.14.1

Signed-off-by: Dapr Bot <daprweb@microsoft.com>
Signed-off-by: sirivarma <siri.varma@outlook.com>

* Add Conversation AI to Java SDK (#1235)

* Conversation first commit

Signed-off-by: Siri Varma Vegiraju <svegiraju@microsoft.com>
Signed-off-by: sirivarma <siri.varma@outlook.com>
Signed-off-by: siri-varma <siri.varma@outlook.com>

* Add unit tests

Signed-off-by: sirivarma <siri.varma@outlook.com>
Signed-off-by: siri-varma <siri.varma@outlook.com>

* change ai to conv

Signed-off-by: sirivarma <siri.varma@outlook.com>
Signed-off-by: siri-varma <siri.varma@outlook.com>

* Move to single module

Signed-off-by: sirivarma <siri.varma@outlook.com>
Signed-off-by: siri-varma <siri.varma@outlook.com>

* Remove module

Signed-off-by: sirivarma <siri.varma@outlook.com>
Signed-off-by: siri-varma <siri.varma@outlook.com>

* Add Integration tests

Signed-off-by: siri-varma <siri.varma@outlook.com>

* Update sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprConversationIT.java

Co-authored-by: Cassie Coyle <cassie.i.coyle@gmail.com>
Signed-off-by: Siri Varma Vegiraju <siri.varma@outlook.com>
Signed-off-by: siri-varma <siri.varma@outlook.com>

* Fix things

Signed-off-by: siri-varma <siri.varma@outlook.com>

* Address comments

Signed-off-by: siri-varma <siri.varma@outlook.com>

* Import tag

Signed-off-by: siri-varma <siri.varma@outlook.com>

* Address comments

Signed-off-by: siri-varma <siri.varma@outlook.com>

* Make common config

Signed-off-by: siri-varma <siri.varma@outlook.com>

* Address comments

Signed-off-by: siri-varma <siri.varma@outlook.com>

* fix constant

Signed-off-by: siri-varma <siri.varma@outlook.com>

* fix constant

Signed-off-by: siri-varma <siri.varma@outlook.com>

* fix constant

Signed-off-by: siri-varma <siri.varma@outlook.com>

* fix s

Signed-off-by: siri-varma <siri.varma@outlook.com>

* Fix things

Signed-off-by: siri-varma <siri.varma@outlook.com>

* Fix things

Signed-off-by: siri-varma <siri.varma@outlook.com>

* Fix things

Signed-off-by: siri-varma <siri.varma@outlook.com>

* Make common config

Signed-off-by: siri-varma <siri.varma@outlook.com>

* Update README.md

Signed-off-by: Siri Varma Vegiraju <siri.varma@outlook.com>

* Update README.md

Signed-off-by: Siri Varma Vegiraju <siri.varma@outlook.com>

---------

Signed-off-by: Siri Varma Vegiraju <svegiraju@microsoft.com>
Signed-off-by: sirivarma <siri.varma@outlook.com>
Signed-off-by: siri-varma <siri.varma@outlook.com>
Signed-off-by: Siri Varma Vegiraju <siri.varma@outlook.com>
Co-authored-by: Cassie Coyle <cassie.i.coyle@gmail.com>
Co-authored-by: Cassie Coyle <cassie@diagrid.io>
Signed-off-by: sirivarma <siri.varma@outlook.com>

* Add docs for usage of Jobs SDK (#1323)

* Add doc for jobs

Signed-off-by: siri-varma <siri.varma@outlook.com>

* Add docs for Jobs

Signed-off-by: siri-varma <siri.varma@outlook.com>

* Apply suggestions from code review

Co-authored-by: Cassie Coyle <cassie.i.coyle@gmail.com>
Signed-off-by: Siri Varma Vegiraju <siri.varma@outlook.com>

---------

Signed-off-by: siri-varma <siri.varma@outlook.com>
Signed-off-by: Siri Varma Vegiraju <siri.varma@outlook.com>
Co-authored-by: artur-ciocanu <artur.ciocanu@gmail.com>
Co-authored-by: Cassie Coyle <cassie.i.coyle@gmail.com>
Signed-off-by: sirivarma <siri.varma@outlook.com>

* Use dapr/durabletask-java (#1336)

* microsoft durabletask-java -> dapr durabletask-java

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

* update another ref

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

* 1.5.2 release

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

* fix import order

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

* Sdk new changes

Signed-off-by: siri-varma <siri.varma@outlook.com>

* Refine workflows

Signed-off-by: siri-varma <siri.varma@outlook.com>

* add ;

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

* rm try

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

---------

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>
Signed-off-by: siri-varma <siri.varma@outlook.com>
Co-authored-by: siri-varma <siri.varma@outlook.com>
Signed-off-by: sirivarma <siri.varma@outlook.com>

* Update master version to 1.16.0-SNAPSHOT

Signed-off-by: Dapr Bot <daprweb@microsoft.com>
Signed-off-by: sirivarma <siri.varma@outlook.com>

* Fix NPE

Signed-off-by: siri-varma <siri.varma@outlook.com>
Signed-off-by: sirivarma <siri.varma@outlook.com>

* Fix NPE

Signed-off-by: siri-varma <siri.varma@outlook.com>
Signed-off-by: sirivarma <siri.varma@outlook.com>

* Fix NPE

Signed-off-by: siri-varma <siri.varma@outlook.com>
Signed-off-by: sirivarma <siri.varma@outlook.com>

* Fix NPE

Signed-off-by: siri-varma <siri.varma@outlook.com>
Signed-off-by: sirivarma <siri.varma@outlook.com>

* Fix NPE

Signed-off-by: siri-varma <siri.varma@outlook.com>
Signed-off-by: sirivarma <siri.varma@outlook.com>

* Fix NPE

Signed-off-by: siri-varma <siri.varma@outlook.com>
Signed-off-by: sirivarma <siri.varma@outlook.com>

* Fix things

Signed-off-by: siri-varma <siri.varma@outlook.com>
Signed-off-by: sirivarma <siri.varma@outlook.com>

* Renaming and exposing connection details (#1341)

Signed-off-by: Artur Ciocanu <ciocanu@adobe.com>
Co-authored-by: Artur Ciocanu <ciocanu@adobe.com>
Signed-off-by: sirivarma <siri.varma@outlook.com>

* [Master] Fix Vulnerabilities (#1354)

* update okio

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

* rm unused dep

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

---------

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>
Signed-off-by: sirivarma <siri.varma@outlook.com>

* Feat Add TLS & mTLS support for gRPC with root CA and insecure mode (#1361)

* feat: Support for GRPC ssl

Signed-off-by: Javier Aliaga <javier@diagrid.io>

* add tests

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

* fix CI

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

* add back else if

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

* channel cleanup

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

* add root ca support

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

* checkstyles

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

* add insecure

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

* fix checkstyles

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

* use InsecureTrustManagerFactory

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

* fix test

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>

---------

Signed-off-by: Javier Aliaga <javier@diagrid.io>
Signed-off-by: Cassandra Coyle <cassie@diagrid.io>
Co-authored-by: Javier Aliaga <javier@diagrid.io>
Signed-off-by: sirivarma <siri.varma@outlook.com>

* Address comments

Signed-off-by: siri-varma <siri.varma@outlook.com>
Signed-off-by: sirivarma <siri.varma@outlook.com>

* Fix things

Signed-off-by: siri-varma <siri.varma@outlook.com>
Signed-off-by: sirivarma <siri.varma@outlook.com>

* Fix things

Signed-off-by: siri-varma <siri.varma@outlook.com>
Signed-off-by: sirivarma <siri.varma@outlook.com>

---------

Signed-off-by: sirivarma <siri.varma@outlook.com>
Signed-off-by: Artur Ciocanu <ciocanu@adobe.com>
Signed-off-by: Siri Varma Vegiraju <siri.varma@outlook.com>
Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Dapr Bot <daprweb@microsoft.com>
Signed-off-by: Siri Varma Vegiraju <svegiraju@microsoft.com>
Signed-off-by: siri-varma <siri.varma@outlook.com>
Signed-off-by: Cassandra Coyle <cassie@diagrid.io>
Signed-off-by: Javier Aliaga <javier@diagrid.io>
Co-authored-by: Matheus Cruz <56329339+mcruzdev@users.noreply.github.com>
Co-authored-by: artur-ciocanu <artur.ciocanu@gmail.com>
Co-authored-by: Artur Ciocanu <ciocanu@adobe.com>
Co-authored-by: Cassie Coyle <cassie@diagrid.io>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Dapr Bot <56698301+dapr-bot@users.noreply.github.com>
Co-authored-by: Dapr Bot <daprweb@microsoft.com>
Co-authored-by: Cassie Coyle <cassie.i.coyle@gmail.com>
Co-authored-by: Javier Aliaga <javier@diagrid.io>
Signed-off-by: salaboy <Salaboy@gmail.com>

* 1.5.4 (#1375)

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>
Signed-off-by: salaboy <Salaboy@gmail.com>

* fixing order Id correlation

Signed-off-by: salaboy <Salaboy@gmail.com>

* fixing waiting time for tests to run

Signed-off-by: salaboy <Salaboy@gmail.com>

* fixing app name

Signed-off-by: salaboy <Salaboy@gmail.com>

* adding app name and removing log lines

Signed-off-by: salaboy <Salaboy@gmail.com>

* Bump codecov/codecov-action from 5.4.2 to 5.4.3 (#1379)

Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5.4.2 to 5.4.3.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v5.4.2...v5.4.3)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-version: 5.4.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Signed-off-by: salaboy <Salaboy@gmail.com>

* Bump fossas/fossa-action from 1.6.0 to 1.7.0 (#1380)

Bumps [fossas/fossa-action](https://github.com/fossas/fossa-action) from 1.6.0 to 1.7.0.
- [Release notes](https://github.com/fossas/fossa-action/releases)
- [Commits](https://github.com/fossas/fossa-action/compare/v1.6.0...v1.7.0)

---
updated-dependencies:
- dependency-name: fossas/fossa-action
  dependency-version: 1.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Cassie Coyle <cassie.i.coyle@gmail.com>
Signed-off-by: salaboy <Salaboy@gmail.com>

* Fix component spec parsing (#1370)

* Fix component spec parsing

Signed-off-by: Deepak <sdeepaksharma15@gmail.com>

* Fix component spec parsing

Signed-off-by: Deepak <sdeepaksharma15@gmail.com>

* Fix component spec metadata parsing

Signed-off-by: Deepak <sdeepaksharma15@gmail.com>

* fix checkstyle-error

Signed-off-by: Deepak <sdeepaksharma15@gmail.com>

---------

Signed-off-by: Deepak <sdeepaksharma15@gmail.com>
Co-authored-by: Cassie Coyle <cassie.i.coyle@gmail.com>
Co-authored-by: artur-ciocanu <artur.ciocanu@gmail.com>
Signed-off-by: salaboy <Salaboy@gmail.com>

* Update spring-boot-examples/workflows/README.md

Co-authored-by: Cassie Coyle <cassie.i.coyle@gmail.com>
Signed-off-by: salaboy <Salaboy@gmail.com>

* fixing comments

Signed-off-by: salaboy <Salaboy@gmail.com>

* Update body.json

Signed-off-by: artur-ciocanu <artur.ciocanu@gmail.com>
Signed-off-by: salaboy <Salaboy@gmail.com>

* Update FanOutInWorkflow.java

Signed-off-by: artur-ciocanu <artur.ciocanu@gmail.com>
Signed-off-by: salaboy <Salaboy@gmail.com>

* clean up logs for multiple executions, for standalone mode

Signed-off-by: salaboy <Salaboy@gmail.com>

* Update application.properties

Signed-off-by: artur-ciocanu <artur.ciocanu@gmail.com>

---------

Signed-off-by: Javier Aliaga <javier@diagrid.io>
Signed-off-by: Cassandra Coyle <cassie@diagrid.io>
Signed-off-by: salaboy <Salaboy@gmail.com>
Signed-off-by: sirivarma <siri.varma@outlook.com>
Signed-off-by: Artur Ciocanu <ciocanu@adobe.com>
Signed-off-by: Siri Varma Vegiraju <siri.varma@outlook.com>
Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Dapr Bot <daprweb@microsoft.com>
Signed-off-by: Siri Varma Vegiraju <svegiraju@microsoft.com>
Signed-off-by: siri-varma <siri.varma@outlook.com>
Signed-off-by: Deepak <sdeepaksharma15@gmail.com>
Signed-off-by: artur-ciocanu <artur.ciocanu@gmail.com>
Co-authored-by: Cassie Coyle <cassie.i.coyle@gmail.com>
Co-authored-by: Javier Aliaga <javier@diagrid.io>
Co-authored-by: Siri Varma Vegiraju <siri.varma@outlook.com>
Co-authored-by: Matheus Cruz <56329339+mcruzdev@users.noreply.github.com>
Co-authored-by: artur-ciocanu <artur.ciocanu@gmail.com>
Co-authored-by: Artur Ciocanu <ciocanu@adobe.com>
Co-authored-by: Cassie Coyle <cassie@diagrid.io>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Dapr Bot <56698301+dapr-bot@users.noreply.github.com>
Co-authored-by: Dapr Bot <daprweb@microsoft.com>
Co-authored-by: iddeepak <87892182+iddeepak@users.noreply.github.com>
This commit is contained in:
salaboy 2025-05-21 20:56:55 +01:00 committed by GitHub
parent c3592b446d
commit 1e4bcf9b9f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 1502 additions and 1 deletions

View File

@ -217,3 +217,9 @@ jobs:
mm.py README.md
env:
DOCKER_HOST: ${{steps.setup_docker.outputs.sock}}
- name: Validate Spring Boot Workflow examples
working-directory: ./spring-boot-examples/workflows
run: |
mm.py README.md
env:
DOCKER_HOST: ${{steps.setup_docker.outputs.sock}}

View File

@ -21,6 +21,7 @@
<modules>
<module>producer-app</module>
<module>consumer-app</module>
<module>workflows</module>
</modules>
<dependencyManagement>

View File

@ -0,0 +1,399 @@
# Dapr Spring Boot Workflow Examples
This application allows you to run different [workflow patterns](https://docs.dapr.io/developing-applications/building-blocks/workflow/workflow-patterns) including:
- Chained Activities
- Parent/Child Workflows
- Continue workflow by sending External Events
- Fan Out/In activities for parallel execution
## Running these examples from source code
To run these examples you will need:
- Java SDK
- Maven
- Docker or a container runtime such as Podman
From the `spring-boot-examples/workflows` directory you can start the service by running the following command:
<!-- STEP
name: Run Demo Workflow Application
match_order: none
output_match_mode: substring
expected_stdout_lines:
- 'Started WorkflowPatternsApplication'
background: true
expected_return_code: 143
sleep: 30
timeout_seconds: 45
-->
<!-- Timeout for above service must be more than sleep + timeout for the client-->
```sh
../../mvnw spring-boot:test-run
```
<!-- END_STEP -->
By running the `spring-boot:test-run` goal, the application is loaded using the [test configurations](src/test/java/io/dapr/springboot/examples/wfp/DaprTestContainersConfig.java)
configured using [Testcontainers](https://testcontainers.com) to boostrap the [Dapr](https://dapr.io) sidecar and control plane.
Once the application is running you can trigger the different patterns by sending the following requests:
### Chaining Activities Workflow example
The `io.dapr.springboot.examples.wfp.chain.ChainWorkflow` executes three chained activities. For this example the
`ToUpperCaseActivity.java` is used to transform to upper case three strings from an array.
```mermaid
graph LR
SW((Start
Workflow))
A1[Activity1]
A2[Activity2]
A3[Activity3]
EW((End
Workflow))
SW --> A1
A1 --> A2
A2 --> A3
A3 --> EW
```
<!-- STEP
name: Start Chain Activities Workflow
match_order: none
output_match_mode: substring
expected_stdout_lines:
- 'TOKYO, LONDON, SEATTLE'
background: true
sleep: 1
timeout_seconds: 2
-->
<!-- Timeout for above service must be more than sleep + timeout for the client-->
To start the workflow with the three chained activities you can run:
```sh
curl -X POST localhost:8080/wfp/chain -H 'Content-Type: application/json'
```
<!-- END_STEP -->
As result from executing the request you should see:
```bash
TOKYO, LONDON, SEATTLE
```
In the application output you should see the workflow activities being executed.
```bash
io.dapr.workflows.WorkflowContext : Starting Workflow: io.dapr.springboot.examples.wfp.chain.ChainWorkflow
i.d.s.e.w.WorkflowPatternsRestController : Workflow instance 7625b4af-8c04-408a-93dc-bad753466e43 started
i.d.s.e.wfp.chain.ToUpperCaseActivity : Starting Activity: io.dapr.springboot.examples.wfp.chain.ToUpperCaseActivity
i.d.s.e.wfp.chain.ToUpperCaseActivity : Message Received from input: Tokyo
i.d.s.e.wfp.chain.ToUpperCaseActivity : Sending message to output: TOKYO
i.d.s.e.wfp.chain.ToUpperCaseActivity : Starting Activity: io.dapr.springboot.examples.wfp.chain.ToUpperCaseActivity
i.d.s.e.wfp.chain.ToUpperCaseActivity : Message Received from input: London
i.d.s.e.wfp.chain.ToUpperCaseActivity : Sending message to output: LONDON
i.d.s.e.wfp.chain.ToUpperCaseActivity : Starting Activity: io.dapr.springboot.examples.wfp.chain.ToUpperCaseActivity
i.d.s.e.wfp.chain.ToUpperCaseActivity : Message Received from input: Seattle
i.d.s.e.wfp.chain.ToUpperCaseActivity : Sending message to output: SEATTLE
io.dapr.workflows.WorkflowContext : Workflow finished with result: TOKYO, LONDON, SEATTLE
```
### Parent / Child Workflows example
In this example we start a Parent workflow that calls a child workflow that execute one activity that reverses an input string.
The Parent workflow looks like this:
```mermaid
graph LR
SW((Start
Workflow))
subgraph for each word in the input
GWL[Call child workflow]
end
EW((End
Workflow))
SW --> GWL
GWL --> EW
```
The Child workflow looks like this:
```mermaid
graph LR
SW((Start
Workflow))
A1[Activity1]
EW((End
Workflow))
SW --> A1
A1 --> EW
```
To start the parent workflow you can run:
<!-- STEP
name: Start Parent/Child Workflow
match_order: none
output_match_mode: substring
expected_stdout_lines:
- '!wolfkroW rpaD olleH'
background: true
sleep: 1
timeout_seconds: 2
-->
<!-- Timeout for above service must be more than sleep + timeout for the client-->
To start the workflow with the three chained activities you can run:
```sh
curl -X POST localhost:8080/wfp/child -H 'Content-Type: application/json'
```
<!-- END_STEP -->
As result from executing the request you should see:
```bash
!wolfkroW rpaD olleH
```
In the application output you should see the workflow activities being executed.
```bash
io.dapr.workflows.WorkflowContext : Starting Workflow: io.dapr.springboot.examples.wfp.child.ParentWorkflow
io.dapr.workflows.WorkflowContext : calling childworkflow with input: Hello Dapr Workflow!
i.d.s.e.w.WorkflowPatternsRestController : Workflow instance f3ec9566-a0fc-4d28-8912-3f3ded3cd8a9 started
io.dapr.workflows.WorkflowContext : Starting ChildWorkflow: io.dapr.springboot.examples.wfp.child.ChildWorkflow
io.dapr.workflows.WorkflowContext : ChildWorkflow received input: Hello Dapr Workflow!
io.dapr.workflows.WorkflowContext : ChildWorkflow is calling Activity: io.dapr.springboot.examples.wfp.child.ReverseActivity
i.d.s.e.wfp.child.ReverseActivity : Starting Activity: io.dapr.springboot.examples.wfp.child.ReverseActivity
i.d.s.e.wfp.child.ReverseActivity : Message Received from input: Hello Dapr Workflow!
i.d.s.e.wfp.child.ReverseActivity : Sending message to output: !wolfkroW rpaD olleH
io.dapr.workflows.WorkflowContext : ChildWorkflow finished with: !wolfkroW rpaD olleH
io.dapr.workflows.WorkflowContext : childworkflow finished with: !wolfkroW rpaD olleH
```
### ContinueAsNew Workflows example
In this example we start a workflow that every 3 seconds schedule a new workflow consistently. This workflow executes
one activity called CleanUpActivity that takes 2 seconds to complete. This loops repeat consistently for 5 times.
To start the workflow you can run:
<!-- STEP
name: Start ContinueAsNew Workflow
match_order: none
output_match_mode: substring
expected_stdout_lines:
- '{"cleanUpTimes":5}'
background: true
sleep: 10
timeout_seconds: 15
-->
<!-- Timeout for above service must be more than sleep + timeout for the client-->
```sh
curl -X POST localhost:8080/wfp/continueasnew -H 'Content-Type: application/json'
```
<!-- END_STEP -->
As result from executing the request you should see:
```bash
{"cleanUpTimes":5}
```
In the application output you should see the workflow activities being executed.
```bash
io.dapr.workflows.WorkflowContext : Starting Workflow: io.dapr.springboot.examples.wfp.continueasnew.ContinueAsNewWorkflow
io.dapr.workflows.WorkflowContext : call CleanUpActivity to do the clean up
i.d.s.e.w.WorkflowPatternsRestController : Workflow instance b808e7d6-ab47-4eba-8188-dc9ff8780764 started
i.d.s.e.w.continueasnew.CleanUpActivity : Starting Activity: io.dapr.springboot.examples.wfp.continueasnew.CleanUpActivity
i.d.s.e.w.continueasnew.CleanUpActivity : start clean up work, it may take few seconds to finish... Time:10:48:45
io.dapr.workflows.WorkflowContext : CleanUpActivity finished
io.dapr.workflows.WorkflowContext : wait 5 seconds for next clean up
io.dapr.workflows.WorkflowContext : Let's do more cleaning.
io.dapr.workflows.WorkflowContext : Starting Workflow: io.dapr.springboot.examples.wfp.continueasnew.ContinueAsNewWorkflow
io.dapr.workflows.WorkflowContext : call CleanUpActivity to do the clean up
i.d.s.e.w.continueasnew.CleanUpActivity : Starting Activity: io.dapr.springboot.examples.wfp.continueasnew.CleanUpActivity
i.d.s.e.w.continueasnew.CleanUpActivity : start clean up work, it may take few seconds to finish... Time:10:48:50
io.dapr.workflows.WorkflowContext : CleanUpActivity finished
io.dapr.workflows.WorkflowContext : wait 5 seconds for next clean up
io.dapr.workflows.WorkflowContext : Let's do more cleaning.
io.dapr.workflows.WorkflowContext : Starting Workflow: io.dapr.springboot.examples.wfp.continueasnew.ContinueAsNewWorkflow
io.dapr.workflows.WorkflowContext : call CleanUpActivity to do the clean up
i.d.s.e.w.continueasnew.CleanUpActivity : Starting Activity: io.dapr.springboot.examples.wfp.continueasnew.CleanUpActivity
i.d.s.e.w.continueasnew.CleanUpActivity : start clean up work, it may take few seconds to finish... Time:10:48:55
io.dapr.workflows.WorkflowContext : CleanUpActivity finished
io.dapr.workflows.WorkflowContext : wait 5 seconds for next clean up
io.dapr.workflows.WorkflowContext : Let's do more cleaning.
io.dapr.workflows.WorkflowContext : Starting Workflow: io.dapr.springboot.examples.wfp.continueasnew.ContinueAsNewWorkflow
io.dapr.workflows.WorkflowContext : call CleanUpActivity to do the clean up
i.d.s.e.w.continueasnew.CleanUpActivity : Starting Activity: io.dapr.springboot.examples.wfp.continueasnew.CleanUpActivity
i.d.s.e.w.continueasnew.CleanUpActivity : start clean up work, it may take few seconds to finish... Time:10:49:0
io.dapr.workflows.WorkflowContext : CleanUpActivity finished
io.dapr.workflows.WorkflowContext : wait 5 seconds for next clean up
io.dapr.workflows.WorkflowContext : Let's do more cleaning.
io.dapr.workflows.WorkflowContext : Starting Workflow: io.dapr.springboot.examples.wfp.continueasnew.ContinueAsNewWorkflow
io.dapr.workflows.WorkflowContext : call CleanUpActivity to do the clean up
i.d.s.e.w.continueasnew.CleanUpActivity : Starting Activity: io.dapr.springboot.examples.wfp.continueasnew.CleanUpActivity
i.d.s.e.w.continueasnew.CleanUpActivity : start clean up work, it may take few seconds to finish... Time:10:49:5
io.dapr.workflows.WorkflowContext : CleanUpActivity finished
io.dapr.workflows.WorkflowContext : wait 5 seconds for next clean up
io.dapr.workflows.WorkflowContext : We did enough cleaning
```
### External Event Workflow example
In this example we start a workflow that as part of its execution waits for an external event to continue. To correlate
workflows and events we use the parameter `orderId`
To start the workflow you can run:
<!-- STEP
name: Start External Event Workflow
match_order: none
output_match_mode: substring
background: true
sleep: 1
timeout_seconds: 2
-->
<!-- Timeout for above service must be more than sleep + timeout for the client-->
```sh
curl -X POST "localhost:8080/wfp/externalevent?orderId=123" -H 'Content-Type: application/json'
```
<!-- END_STEP -->
In the application output you should see the workflow activities being executed.
```bash
io.dapr.workflows.WorkflowContext : Starting Workflow: io.dapr.springboot.examples.wfp.externalevent.ExternalEventWorkflow
io.dapr.workflows.WorkflowContext : Waiting for approval...
i.d.s.e.w.WorkflowPatternsRestController : Workflow instance 8a55cf6d-9059-49b1-8c83-fbe17567a02e started
```
You should see the Workflow ID that was created, in this example you don't need to remember this id,
as you can use the orderId to find the right instance.
When you are ready to approve the order you can send the following request:
<!-- STEP
name: Send External Event
match_order: none
output_match_mode: substring
expected_stdout_lines:
- '{"approved":true}'
background: true
sleep: 5
timeout_seconds: 10
-->
<!-- Timeout for above service must be more than sleep + timeout for the client-->
To send the event you can run:
```sh
curl -X POST "localhost:8080/wfp/externalevent-continue?orderId=123&decision=true" -H 'Content-Type: application/json'
```
<!-- END_STEP -->
```bash
{"approved":true}
```
In the application output you should see the workflow activities being executed.
```bash
i.d.s.e.w.WorkflowPatternsRestController : Workflow instance e86bc464-6166-434d-8c91-d99040d6f54e continue
io.dapr.workflows.WorkflowContext : approval granted - do the approved action
i.d.s.e.w.externalevent.ApproveActivity : Starting Activity: io.dapr.springboot.examples.wfp.externalevent.ApproveActivity
i.d.s.e.w.externalevent.ApproveActivity : Running approval activity...
io.dapr.workflows.WorkflowContext : approval-activity finished
```
### Fan Out/In Workflow example
In this example we start a workflow that takes an ArrayList of strings and calls one activity per item in the ArrayList. The activities
are executed and the workflow waits for all of them to complete to aggregate the results.
```mermaid
graph LR
SW((Start
Workflow))
subgraph for each word in the input
GWL[GetWordLength]
end
ALL[Wait until all tasks
are completed]
EW((End
Workflow))
SW --> GWL
GWL --> ALL
ALL --> EW
```
To start the workflow you can run:
<!-- STEP
name: Start Parent/Child Workflow
match_order: none
output_match_mode: substring
expected_stdout_lines:
- '{"wordCount":60}'
background: true
sleep: 1
timeout_seconds: 2
-->
<!-- Timeout for above service must be more than sleep + timeout for the client-->
```sh
curl -X POST localhost:8080/wfp/fanoutin -H 'Content-Type: application/json' -d @body.json
```
<!-- END_STEP -->
As result from executing the request you should see:
```bash
{"wordCount":60}
```
In the application output you should see the workflow activities being executed.
```bash
io.dapr.workflows.WorkflowContext : Starting Workflow: io.dapr.springboot.examples.wfp.fanoutin.FanOutInWorkflow
i.d.s.e.w.WorkflowPatternsRestController : Workflow instance a771a7ba-f9fb-4399-aaee-a2fb0b102e5d started
i.d.s.e.wfp.fanoutin.CountWordsActivity : Starting Activity: io.dapr.springboot.examples.wfp.fanoutin.CountWordsActivity
i.d.s.e.wfp.fanoutin.CountWordsActivity : Starting Activity: io.dapr.springboot.examples.wfp.fanoutin.CountWordsActivity
i.d.s.e.wfp.fanoutin.CountWordsActivity : Starting Activity: io.dapr.springboot.examples.wfp.fanoutin.CountWordsActivity
i.d.s.e.wfp.fanoutin.CountWordsActivity : Activity returned: 2.
i.d.s.e.wfp.fanoutin.CountWordsActivity : Activity finished
i.d.s.e.wfp.fanoutin.CountWordsActivity : Activity returned: 11.
i.d.s.e.wfp.fanoutin.CountWordsActivity : Activity returned: 17.
i.d.s.e.wfp.fanoutin.CountWordsActivity : Activity finished
i.d.s.e.wfp.fanoutin.CountWordsActivity : Activity finished
i.d.s.e.wfp.fanoutin.CountWordsActivity : Starting Activity: io.dapr.springboot.examples.wfp.fanoutin.CountWordsActivity
i.d.s.e.wfp.fanoutin.CountWordsActivity : Activity returned: 21.
i.d.s.e.wfp.fanoutin.CountWordsActivity : Activity finished
i.d.s.e.wfp.fanoutin.CountWordsActivity : Starting Activity: io.dapr.springboot.examples.wfp.fanoutin.CountWordsActivity
i.d.s.e.wfp.fanoutin.CountWordsActivity : Activity returned: 9.
i.d.s.e.wfp.fanoutin.CountWordsActivity : Activity finished
io.dapr.workflows.WorkflowContext : Workflow finished with result: 60
```
## Testing workflow executions
Workflow execution can be tested using Testcontainers and you can find all the tests for the patterns covered in this
application [here](test/java/io/dapr/springboot/examples/wfp/TestWorkflowPatternsApplication.java).

View File

@ -0,0 +1,5 @@
["Hello, world!",
"The quick brown fox jumps over the lazy dog.",
"If a tree falls in the forest and there is no one there to hear it, does it make a sound?",
"The greatest glory in living lies not in never falling, but in rising every time we fall.",
"Always remember that you are absolutely unique. Just like everyone else."]

View File

@ -0,0 +1,68 @@
<?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>io.dapr</groupId>
<artifactId>spring-boot-examples</artifactId>
<version>0.16.0-SNAPSHOT</version>
</parent>
<artifactId>workflows</artifactId>
<name>workflows</name>
<description>Spring Boot, Testcontainers and Dapr Integration Examples :: Workflows</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</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>
</dependency>
<dependency>
<groupId>io.dapr.spring</groupId>
<artifactId>dapr-spring-boot-starter-test</artifactId>
<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>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-site-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<configuration>
<!-- Skip checkstyle for auto-generated code -->
<skip>true</skip>
</configuration>
</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.wfp;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class WorkflowPatternsApplication {
public static void main(String[] args) {
SpringApplication.run(WorkflowPatternsApplication.class, args);
}
}

View File

@ -0,0 +1,26 @@
/*
* 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.wfp;
import io.dapr.springboot.examples.wfp.continueasnew.CleanUpLog;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class WorkflowPatternsConfiguration {
@Bean
public CleanUpLog cleanUpLog(){
return new CleanUpLog();
}
}

View File

@ -0,0 +1,140 @@
/*
* 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.wfp;
import io.dapr.spring.workflows.config.EnableDaprWorkflows;
import io.dapr.springboot.examples.wfp.chain.ChainWorkflow;
import io.dapr.springboot.examples.wfp.child.ParentWorkflow;
import io.dapr.springboot.examples.wfp.continueasnew.CleanUpLog;
import io.dapr.springboot.examples.wfp.continueasnew.ContinueAsNewWorkflow;
import io.dapr.springboot.examples.wfp.externalevent.Decision;
import io.dapr.springboot.examples.wfp.externalevent.ExternalEventWorkflow;
import io.dapr.springboot.examples.wfp.fanoutin.FanOutInWorkflow;
import io.dapr.springboot.examples.wfp.fanoutin.Result;
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.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeoutException;
@RestController
@EnableDaprWorkflows
public class WorkflowPatternsRestController {
private final Logger logger = LoggerFactory.getLogger(WorkflowPatternsRestController.class);
@Autowired
private DaprWorkflowClient daprWorkflowClient;
@Autowired
private CleanUpLog cleanUpLog;
private Map<String, String> ordersToApprove = new HashMap<>();
/**
* Run Chain Demo Workflow
* @return the output of the ChainWorkflow execution
*/
@PostMapping("wfp/chain")
public String chain() throws TimeoutException {
String instanceId = daprWorkflowClient.scheduleNewWorkflow(ChainWorkflow.class);
logger.info("Workflow instance " + instanceId + " started");
return daprWorkflowClient
.waitForInstanceCompletion(instanceId, Duration.ofSeconds(2), true)
.readOutputAs(String.class);
}
/**
* Run Child Demo Workflow
* @return confirmation that the workflow instance was created for the workflow pattern child
*/
@PostMapping("wfp/child")
public String child() throws TimeoutException {
String instanceId = daprWorkflowClient.scheduleNewWorkflow(ParentWorkflow.class);
logger.info("Workflow instance " + instanceId + " started");
return daprWorkflowClient
.waitForInstanceCompletion(instanceId, Duration.ofSeconds(2), true)
.readOutputAs(String.class);
}
/**
* Run Fan Out/in Demo Workflow
* @return confirmation that the workflow instance was created for the workflow pattern faninout
*/
@PostMapping("wfp/fanoutin")
public Result fanOutIn(@RequestBody List<String> listOfStrings) throws TimeoutException {
String instanceId = daprWorkflowClient.scheduleNewWorkflow(FanOutInWorkflow.class, listOfStrings);
logger.info("Workflow instance " + instanceId + " started");
// Block until the orchestration completes. Then print the final status, which includes the output.
WorkflowInstanceStatus workflowInstanceStatus = daprWorkflowClient.waitForInstanceCompletion(
instanceId,
Duration.ofSeconds(30),
true);
logger.info("workflow instance with ID: %s completed with result: %s%n", instanceId,
workflowInstanceStatus.readOutputAs(Result.class));
return workflowInstanceStatus.readOutputAs(Result.class);
}
/**
* Run External Event Workflow Pattern
* @return confirmation that the workflow instance was created for the workflow pattern externalevent
*/
@PostMapping("wfp/externalevent")
public String externalEvent(@RequestParam("orderId") String orderId) {
String instanceId = daprWorkflowClient.scheduleNewWorkflow(ExternalEventWorkflow.class);
ordersToApprove.put(orderId, instanceId);
logger.info("Workflow instance " + instanceId + " started");
return instanceId;
}
@PostMapping("wfp/externalevent-continue")
public Decision externalEventContinue(@RequestParam("orderId") String orderId, @RequestParam("decision") Boolean decision)
throws TimeoutException {
String instanceId = ordersToApprove.get(orderId);
logger.info("Workflow instance " + instanceId + " continue");
daprWorkflowClient.raiseEvent(instanceId, "Approval", decision);
WorkflowInstanceStatus workflowInstanceStatus = daprWorkflowClient
.waitForInstanceCompletion(instanceId, null, true);
return workflowInstanceStatus.readOutputAs(Decision.class);
}
@PostMapping("wfp/continueasnew")
public CleanUpLog continueAsNew()
throws TimeoutException {
cleanUpLog.clearLog();
String instanceId = daprWorkflowClient.scheduleNewWorkflow(ContinueAsNewWorkflow.class);
logger.info("Workflow instance " + instanceId + " started");
WorkflowInstanceStatus workflowInstanceStatus = daprWorkflowClient.waitForInstanceCompletion(instanceId, null, true);
System.out.printf("workflow instance with ID: %s completed.", instanceId);
return workflowInstanceStatus.readOutputAs(CleanUpLog.class);
}
}

View File

@ -0,0 +1,36 @@
/*
* 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.wfp.chain;
import io.dapr.workflows.Workflow;
import io.dapr.workflows.WorkflowStub;
import org.springframework.stereotype.Component;
@Component
public class ChainWorkflow implements Workflow {
@Override
public WorkflowStub create() {
return ctx -> {
ctx.getLogger().info("Starting Workflow: " + ctx.getName());
String result = "";
result += ctx.callActivity(ToUpperCaseActivity.class.getName(), "Tokyo", String.class).await() + ", ";
result += ctx.callActivity(ToUpperCaseActivity.class.getName(), "London", String.class).await() + ", ";
result += ctx.callActivity(ToUpperCaseActivity.class.getName(), "Seattle", String.class).await();
ctx.getLogger().info("Workflow finished with result: " + result);
ctx.complete(result);
};
}
}

View File

@ -0,0 +1,38 @@
/*
* 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.wfp.chain;
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 ToUpperCaseActivity implements WorkflowActivity {
@Override
public Object run(WorkflowActivityContext ctx) {
Logger logger = LoggerFactory.getLogger(ToUpperCaseActivity.class);
logger.info("Starting Activity: " + ctx.getName());
var message = ctx.getInput(String.class);
var newMessage = message.toUpperCase();
logger.info("Message Received from input: " + message);
logger.info("Sending message to output: " + newMessage);
return newMessage;
}
}

View File

@ -0,0 +1,37 @@
/*
* 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.wfp.child;
import io.dapr.workflows.Workflow;
import io.dapr.workflows.WorkflowStub;
import org.springframework.stereotype.Component;
@Component
public class ChildWorkflow implements Workflow {
@Override
public WorkflowStub create() {
return ctx -> {
ctx.getLogger().info("Starting ChildWorkflow: " + ctx.getName());
var childWorkflowInput = ctx.getInput(String.class);
ctx.getLogger().info("ChildWorkflow received input: " + childWorkflowInput);
ctx.getLogger().info("ChildWorkflow is calling Activity: " + ReverseActivity.class.getName());
String result = ctx.callActivity(ReverseActivity.class.getName(), childWorkflowInput, String.class).await();
ctx.getLogger().info("ChildWorkflow finished with: " + result);
ctx.complete(result);
};
}
}

View File

@ -0,0 +1,37 @@
/*
* 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.wfp.child;
import io.dapr.workflows.Workflow;
import io.dapr.workflows.WorkflowStub;
import org.springframework.stereotype.Component;
@Component
public class ParentWorkflow implements Workflow {
@Override
public WorkflowStub create() {
return ctx -> {
ctx.getLogger().info("Starting Workflow: " + ctx.getName());
var childWorkflowInput = "Hello Dapr Workflow!";
ctx.getLogger().info("calling childworkflow with input: " + childWorkflowInput);
var childWorkflowOutput =
ctx.callChildWorkflow(ChildWorkflow.class.getName(), childWorkflowInput, String.class).await();
ctx.getLogger().info("childworkflow finished with: " + childWorkflowOutput);
ctx.complete(childWorkflowOutput);
};
}
}

View File

@ -0,0 +1,37 @@
/*
* 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.wfp.child;
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 ReverseActivity implements WorkflowActivity {
@Override
public Object run(WorkflowActivityContext ctx) {
Logger logger = LoggerFactory.getLogger(ReverseActivity.class);
logger.info("Starting Activity: " + ctx.getName());
var message = ctx.getInput(String.class);
var newMessage = new StringBuilder(message).reverse().toString();
logger.info("Message Received from input: " + message);
logger.info("Sending message to output: " + newMessage);
return newMessage;
}
}

View File

@ -0,0 +1,52 @@
/*
* 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.wfp.continueasnew;
import io.dapr.workflows.WorkflowActivity;
import io.dapr.workflows.WorkflowActivityContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.concurrent.TimeUnit;
@Component
public class CleanUpActivity implements WorkflowActivity {
@Autowired
private CleanUpLog cleanUpLog;
@Override
public Object run(WorkflowActivityContext ctx) {
Logger logger = LoggerFactory.getLogger(CleanUpActivity.class);
logger.info("Starting Activity: " + ctx.getName());
LocalDateTime now = LocalDateTime.now();
String cleanUpTimeString = now.getHour() + ":" + now.getMinute() + ":" + now.getSecond();
logger.info("start clean up work, it may take few seconds to finish... Time:" + cleanUpTimeString);
//Sleeping for 2 seconds to simulate long running operation
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
cleanUpLog.increment();
return "clean up finish.";
}
}

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.wfp.continueasnew;
public class CleanUpLog {
private Integer cleanUpTimes = 0;
public CleanUpLog() {
}
public void increment() {
this.cleanUpTimes += 1;
}
public Integer getCleanUpTimes() {
return cleanUpTimes;
}
public void clearLog(){
this.cleanUpTimes = 0;
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright 2023 The Dapr Authors
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
limitations under the License.
*/
package io.dapr.springboot.examples.wfp.continueasnew;
import io.dapr.workflows.Workflow;
import io.dapr.workflows.WorkflowStub;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.Duration;
@Component
public class ContinueAsNewWorkflow implements Workflow {
/*
Compared with a CRON schedule, this periodic workflow example will never overlap.
For example, a CRON schedule that executes a cleanup every hour will execute it at 1:00, 2:00, 3:00 etc.
and could potentially run into overlap issues if the cleanup takes longer than an hour.
In this example, however, if the cleanup takes 30 minutes, and we create a timer for 1 hour between cleanups,
then it will be scheduled at 1:00, 2:30, 4:00, etc. and there is no chance of overlap.
*/
@Autowired
private CleanUpLog cleanUpLog;
@Override
public WorkflowStub create() {
return ctx -> {
ctx.getLogger().info("Starting Workflow: " + ctx.getName());
ctx.getLogger().info("call CleanUpActivity to do the clean up");
ctx.callActivity(CleanUpActivity.class.getName(), cleanUpLog).await();
ctx.getLogger().info("CleanUpActivity finished");
ctx.getLogger().info("wait 5 seconds for next clean up");
ctx.createTimer(Duration.ofSeconds(3)).await();
if(cleanUpLog.getCleanUpTimes() < 5) {
// continue the workflow.
ctx.getLogger().info("Let's do more cleaning.");
ctx.continueAsNew(null);
} else{
ctx.getLogger().info("We did enough cleaning");
ctx.complete(cleanUpLog);
}
};
}
}

View File

@ -0,0 +1,41 @@
/*
* 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.wfp.externalevent;
import io.dapr.workflows.WorkflowActivity;
import io.dapr.workflows.WorkflowActivityContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
public class ApproveActivity implements WorkflowActivity {
@Override
public Object run(WorkflowActivityContext ctx) {
Logger logger = LoggerFactory.getLogger(ApproveActivity.class);
logger.info("Starting Activity: " + ctx.getName());
logger.info("Running approval activity...");
//Sleeping for 5 seconds to simulate long running operation
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return new Decision(true);
}
}

View File

@ -0,0 +1,33 @@
/*
* 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.wfp.externalevent;
public class Decision {
private Boolean approved;
public Decision() {
}
public Decision(Boolean approved) {
this.approved = approved;
}
public Boolean getApproved() {
return approved;
}
public void setApproved(Boolean approved) {
this.approved = approved;
}
}

View File

@ -0,0 +1,41 @@
/*
* 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.wfp.externalevent;
import io.dapr.workflows.WorkflowActivity;
import io.dapr.workflows.WorkflowActivityContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
public class DenyActivity implements WorkflowActivity {
@Override
public Object run(WorkflowActivityContext ctx) {
Logger logger = LoggerFactory.getLogger(DenyActivity.class);
logger.info("Starting Activity: " + ctx.getName());
logger.info("Running denied activity...");
//Sleeping for 5 seconds to simulate long running operation
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return new Decision(false);
}
}

View File

@ -0,0 +1,43 @@
/*
* 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.wfp.externalevent;
import io.dapr.workflows.Workflow;
import io.dapr.workflows.WorkflowStub;
import org.springframework.stereotype.Component;
@Component
public class ExternalEventWorkflow implements Workflow {
@Override
public WorkflowStub create() {
return ctx -> {
ctx.getLogger().info("Starting Workflow: " + ctx.getName());
ctx.getLogger().info("Waiting for approval...");
Boolean approved = ctx.waitForExternalEvent("Approval", boolean.class).await();
Decision decision = null;
if (approved) {
ctx.getLogger().info("approval granted - do the approved action");
decision = ctx.callActivity(ApproveActivity.class.getName(), Decision.class).await();
ctx.getLogger().info("approval-activity finished");
} else {
ctx.getLogger().info("approval denied - send a notification");
decision = ctx.callActivity(DenyActivity.class.getName(), Decision.class).await();
ctx.getLogger().info("denied-activity finished");
}
ctx.complete(decision);
};
}
}

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.wfp.fanoutin;
import io.dapr.workflows.WorkflowActivity;
import io.dapr.workflows.WorkflowActivityContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.util.StringTokenizer;
@Component
public class CountWordsActivity implements WorkflowActivity {
@Override
public Object run(WorkflowActivityContext ctx) {
Logger logger = LoggerFactory.getLogger(CountWordsActivity.class);
logger.info("Starting Activity: {}", ctx.getName());
String input = ctx.getInput(String.class);
StringTokenizer tokenizer = new StringTokenizer(input);
int result = tokenizer.countTokens();
logger.info("Activity returned: {}.", result);
logger.info("Activity finished");
return result;
}
}

View File

@ -0,0 +1,51 @@
/*
* Copyright 2023 The Dapr Authors
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
limitations under the License.
*/
package io.dapr.springboot.examples.wfp.fanoutin;
import io.dapr.durabletask.Task;
import io.dapr.workflows.Workflow;
import io.dapr.workflows.WorkflowStub;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.stream.Collectors;
@Component
public class FanOutInWorkflow implements Workflow {
@Override
public WorkflowStub create() {
return ctx -> {
ctx.getLogger().info("Starting Workflow: " + ctx.getName());
// The input is a list of objects that need to be operated on.
// In this example, inputs are expected to be strings.
List<?> inputs = ctx.getInput(List.class);
// Fan-out to multiple concurrent activity invocations, each of which does a word count.
List<Task<Integer>> tasks = inputs.stream()
.map(input -> ctx.callActivity(CountWordsActivity.class.getName(), input.toString(), Integer.class))
.collect(Collectors.toList());
// Fan-in to get the total word count from all the individual activity results.
List<Integer> allWordCountResults = ctx.allOf(tasks).await();
int totalWordCount = allWordCountResults.stream().mapToInt(Integer::intValue).sum();
ctx.getLogger().info("Workflow finished with result: " + totalWordCount);
// Save the final result as the orchestration output.
ctx.complete(new Result(totalWordCount));
};
}
}

View File

@ -0,0 +1,33 @@
/*
* 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.wfp.fanoutin;
public class Result {
private Integer wordCount;
public Result() {
}
public Result(Integer wordCount) {
this.wordCount = wordCount;
}
public Integer getWordCount() {
return wordCount;
}
public void setWordCount(Integer wordCount) {
this.wordCount = wordCount;
}
};

View File

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

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.wfp;
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.wfp;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class TestWorkflowPatternsApplication {
public static void main(String[] args) {
SpringApplication.from(WorkflowPatternsApplication::main)
.with(DaprTestContainersConfig.class)
.run(args);
org.testcontainers.Testcontainers.exposeHostPorts(8080);
}
}

View File

@ -0,0 +1,142 @@
/*
* 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.wfp;
import io.dapr.client.DaprClient;
import io.dapr.springboot.DaprAutoConfiguration;
import io.dapr.springboot.examples.wfp.continueasnew.CleanUpLog;
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 java.util.Arrays;
import java.util.List;
import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.jupiter.api.Assertions.assertEquals;
@SpringBootTest(classes = {TestWorkflowPatternsApplication.class, DaprTestContainersConfig.class,
DaprAutoConfiguration.class, },
webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
class WorkflowPatternsAppTests {
@Autowired
private DaprClient daprClient;
@BeforeEach
void setUp() {
RestAssured.baseURI = "http://localhost:" + 8080;
org.testcontainers.Testcontainers.exposeHostPorts(8080);
}
@Test
void testChainWorkflow() {
given().contentType(ContentType.JSON)
.body("")
.when()
.post("/wfp/chain")
.then()
.statusCode(200).body(containsString("TOKYO, LONDON, SEATTLE"));
}
@Test
void testChildWorkflow() {
given().contentType(ContentType.JSON)
.body("")
.when()
.post("/wfp/child")
.then()
.statusCode(200).body(containsString("!wolfkroW rpaD olleH"));
}
@Test
void testFanOutIn() {
List<String> listOfStrings = Arrays.asList(
"Hello, world!",
"The quick brown fox jumps over the lazy dog.",
"If a tree falls in the forest and there is no one there to hear it, does it make a sound?",
"The greatest glory in living lies not in never falling, but in rising every time we fall.",
"Always remember that you are absolutely unique. Just like everyone else.");
given().contentType(ContentType.JSON)
.body(listOfStrings)
.when()
.post("/wfp/fanoutin")
.then()
.statusCode(200).body("wordCount",equalTo(60));
}
@Test
void testExternalEventApprove() {
given()
.queryParam("orderId", "123")
.when()
.post("/wfp/externalevent")
.then()
.statusCode(200).extract().asString();
given()
.queryParam("orderId", "123")
.queryParam("decision", true)
.when()
.post("/wfp/externalevent-continue")
.then()
.statusCode(200).body("approved", equalTo(true));
}
@Test
void testExternalEventDeny() {
given()
.queryParam("orderId", "123")
.when()
.post("/wfp/externalevent")
.then()
.statusCode(200).extract().asString();
given()
.queryParam("orderId", "123")
.queryParam("decision", false)
.when()
.post("/wfp/externalevent-continue")
.then()
.statusCode(200).body("approved", equalTo(false));
}
@Test
void testContinueAsNew() {
//This call blocks until all the clean up activities are executed
CleanUpLog cleanUpLog = given().contentType(ContentType.JSON)
.body("")
.when()
.post("/wfp/continueasnew")
.then()
.statusCode(200).extract().as(CleanUpLog.class);
assertEquals(5, cleanUpLog.getCleanUpTimes());
}
}

View File

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