Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding producer and consumer app examples for Spring Boot integration #1208

Merged
merged 42 commits into from
Mar 1, 2025
Merged
Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
e8ad2db
adding spring boot producer
salaboy Feb 4, 2025
44524bb
adding consumer app
salaboy Feb 4, 2025
4e90a97
increasing wait for events to popup
salaboy Feb 4, 2025
1590793
adding readme and examples
salaboy Feb 5, 2025
4aadb88
aligning tests for examples
salaboy Feb 5, 2025
509a041
increasing time out
salaboy Feb 6, 2025
93eb3b9
adding health check from the sidecar
salaboy Feb 7, 2025
680aeac
feat: Adding basic HTTPEndpoint configuration support in testcontaine…
lbroudoux Feb 6, 2025
7de9850
updating example
salaboy Feb 9, 2025
c97849b
fixing example
salaboy Feb 10, 2025
f79fc28
Add app health check support to Dapr Testcontainer (#1213)
artur-ciocanu Feb 10, 2025
4b53b7a
commenting reuse
salaboy Feb 10, 2025
af6f309
adding how to run on Kubernetes, unfortunately we need to create cont…
salaboy Feb 10, 2025
6420b3c
removing subscription and fixing scopes
salaboy Feb 10, 2025
1312c0b
adding license headers and logger
salaboy Feb 11, 2025
ae0596f
updating logs
salaboy Feb 11, 2025
7421ee9
updating READMEs and update_sdk_version for new module
salaboy Feb 11, 2025
61a714b
removing old line
salaboy Feb 11, 2025
99c6222
removing sleeps, using Wait
salaboy Feb 11, 2025
bb1fb60
updating Kubernetes tutorial to use local registry with KIND, and pro…
salaboy Feb 11, 2025
f2087ed
adding new lines and formatting
salaboy Feb 13, 2025
200a695
updating reuse and removing comments
salaboy Feb 13, 2025
30b3084
fixing reuse
salaboy Feb 13, 2025
77f1e9c
removing line break
salaboy Feb 13, 2025
9302c60
fixing custom line breaks
salaboy Feb 13, 2025
2cf4f3e
fixing xml indent to 2 spaces
salaboy Feb 13, 2025
6b2a8bb
Update spring-boot-examples/README.md
salaboy Feb 19, 2025
f51f8f8
Update spring-boot-examples/README.md
salaboy Feb 19, 2025
dadae31
Update spring-boot-examples/README.md
salaboy Feb 19, 2025
235229c
Update spring-boot-examples/README.md
salaboy Feb 19, 2025
b8a8613
Update spring-boot-examples/README.md
salaboy Feb 19, 2025
ab9e334
Update spring-boot-examples/consumer-app/src/test/java/io/dapr/spring…
salaboy Feb 19, 2025
4467998
Update spring-boot-examples/producer-app/src/test/java/io/dapr/spring…
salaboy Feb 20, 2025
4bc53b6
adding license header to missing files
salaboy Feb 20, 2025
82621ad
adding automated testing for spring boot example
salaboy Feb 21, 2025
afc22fb
adding sb examples to the validation pipeline
salaboy Feb 21, 2025
363a518
updating timeouts
salaboy Feb 21, 2025
6c44695
updating return codes
salaboy Feb 25, 2025
63f76d1
Merge branch 'master' into 1207-sb-examples
cicoyle Feb 25, 2025
70b7006
Merge branch 'master' into 1207-sb-examples
artursouza Feb 28, 2025
f293f74
Merge branch 'master' into 1207-sb-examples
artursouza Feb 28, 2025
5f4e676
Merge branch 'master' into 1207-sb-examples
artur-ciocanu Mar 1, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/scripts/update_sdk_version.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,7 @@ mvn versions:set -DnewVersion=$DAPR_JAVA_SDK_ALPHA_VERSION -f testcontainers-dap
# dapr-spring
mvn versions:set -DnewVersion=$DAPR_JAVA_SDK_ALPHA_VERSION -f dapr-spring/pom.xml

# spring-boot-examples
mvn versions:set -DnewVersion=$DAPR_JAVA_SDK_ALPHA_VERSION -f spring-boot-examples/pom.xml

git clean -f
4 changes: 4 additions & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,7 @@ jobs:
working-directory: ./examples
run: |
mm.py ./src/main/java/io/dapr/examples/pubsub/stream/README.md
- name: Validate Spring Boot examples
working-directory: ./spring-boot-examples
run: |
mm.py README.md
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ This is the Dapr SDK for Java, including the following features:
* Binding
* State Store
* Actors
* Workflows

## Getting Started

Expand Down Expand Up @@ -112,6 +113,13 @@ Try the following examples to learn more about Dapr's Java SDK:
* [Exception handling](./examples/src/main/java/io/dapr/examples/exception)
* [Unit testing](./examples/src/main/java/io/dapr/examples/unittesting)

### Running Spring Boot examples

The Spring Boot integration for Dapr use [Testcontainers](https://testcontainers.com) to set up a local environment development flow that doesn't
require the use of the `dapr` CLI and it integrates with the Spring Boot programming model.

You can find a [step-by-step tutorial showing this integration here](./spring-boot-examples/README.md).

### API Documentation

Please, refer to our [Javadoc](https://dapr.github.io/java-sdk/) website.
Expand Down
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,7 @@
<module>sdk-springboot</module>
<module>dapr-spring</module>
<module>examples</module>
<module>spring-boot-examples</module>
<!-- We are following test containers artifact convention on purpose, don't rename -->
<module>testcontainers-dapr</module>
</modules>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,10 @@ public class DaprContainerIT {

@Container
private static final DaprContainer DAPR_CONTAINER = new DaprContainer("daprio/daprd")
.withAppName("dapr-app")
.withAppPort(8081)
.withAppChannelAddress("host.testcontainers.internal");
.withAppName("dapr-app")
.withAppPort(8081)
.withAppHealthCheckPath("/actuator/health")
.withAppChannelAddress("host.testcontainers.internal");

/**
* Sets the Dapr properties for the test.
Expand All @@ -76,32 +77,35 @@ public void setDaprProperties() {
}

private void configStub() {
stubFor(any(urlMatching("/actuator/health"))
.willReturn(aResponse().withBody("[]").withStatus(200)));

stubFor(any(urlMatching("/dapr/subscribe"))
.willReturn(aResponse().withBody("[]").withStatus(200)));
.willReturn(aResponse().withBody("[]").withStatus(200)));

stubFor(get(urlMatching("/dapr/config"))
.willReturn(aResponse().withBody("[]").withStatus(200)));
.willReturn(aResponse().withBody("[]").withStatus(200)));

stubFor(any(urlMatching("/([a-z1-9]*)"))
.willReturn(aResponse().withBody("[]").withStatus(200)));
.willReturn(aResponse().withBody("[]").withStatus(200)));

// create a stub
stubFor(post(urlEqualTo("/events"))
.willReturn(aResponse().withBody("event received!").withStatus(200)));
.willReturn(aResponse().withBody("event received!").withStatus(200)));

configureFor("localhost", 8081);
}

@Test
public void testDaprContainerDefaults() {
assertEquals(2,
DAPR_CONTAINER.getComponents().size(),
"The pubsub and kvstore component should be configured by default"
DAPR_CONTAINER.getComponents().size(),
"The pubsub and kvstore component should be configured by default"
);
assertEquals(
1,
DAPR_CONTAINER.getSubscriptions().size(),
"A subscription should be configured by default if none is provided"
1,
DAPR_CONTAINER.getSubscriptions().size(),
"A subscription should be configured by default if none is provided"
);
}

Expand All @@ -128,10 +132,10 @@ public void testPlacement() throws Exception {
Thread.sleep(1000);

OkHttpClient okHttpClient = new OkHttpClient.Builder()
.build();
.build();
Request request = new Request.Builder()
.url(DAPR_CONTAINER.getHttpEndpoint() + "/v1.0/metadata")
.build();
.url(DAPR_CONTAINER.getHttpEndpoint() + "/v1.0/metadata")
.build();

try (Response response = okHttpClient.newCall(request).execute()) {
if (response.isSuccessful() && response.body() != null) {
Expand All @@ -157,7 +161,7 @@ public void testPubSub() throws Exception {

private DaprClientBuilder createDaprClientBuilder() {
return new DaprClientBuilder()
.withPropertyOverride(Properties.HTTP_ENDPOINT, DAPR_CONTAINER.getHttpEndpoint())
.withPropertyOverride(Properties.GRPC_ENDPOINT, DAPR_CONTAINER.getGrpcEndpoint());
.withPropertyOverride(Properties.HTTP_ENDPOINT, DAPR_CONTAINER.getHttpEndpoint())
.withPropertyOverride(Properties.GRPC_ENDPOINT, DAPR_CONTAINER.getGrpcEndpoint());
}
}
176 changes: 176 additions & 0 deletions spring-boot-examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
# Dapr Spring Boot and Testcontainers integration Example

This example consists of two applications:
- Producer App:
- Publish messages using a Spring Messaging approach
- Store and retrieve information using Spring Data CrudRepository
- Implements a Workflow with Dapr Workflows
- Consumer App:
- Subscribe to messages

## 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/` directory you can start each service using the test configuration that uses
[Testcontainers](https://testcontainers.com) to boostrap [Dapr](https://dapr.io) by running the following command:

<!-- STEP
name: Run Demo Producer Service
match_order: none
output_match_mode: substring
expected_stdout_lines:
- 'Started ProducerApplication'
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
cd producer-app/
../../mvnw -Dspring-boot.run.arguments="--reuse=true" spring-boot:test-run
```

<!-- END_STEP -->

This will start the `producer-app` with Dapr services and the infrastructure needed by the application to run,
in this case RabbitMQ and PostgreSQL. The `producer-app` starts on port `8080` by default.

The `-Dspring-boot.run.arguments="--reuse=true"` flag helps the application to connect to an existing shared
infrastructure if it already exists. For development purposes, and to connect both applications we will set the flag
in both. For more details check the `DaprTestContainersConfig.java` classes in both, the `producer-app` and the `consumer-app`.

Then run in a different terminal:

<!-- STEP
name: Run Demo Consumer Service
match_order: none
output_match_mode: substring
expected_stdout_lines:
- 'Started ConsumerApplication'
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
cd consumer-app/
../../mvnw -Dspring-boot.run.arguments="--reuse=true" spring-boot:test-run
```

<!-- END_STEP -->
The `consumer-app` starts in port `8081` by default.

## Interacting with the applications

Now that both applications are up you can place an order by sending a POST request to `:8080/orders/`
You can use `curl` to send a POST request to the `producer-app`:


<!-- STEP
name: Send POST request to Producer App
match_order: none
output_match_mode: substring
expected_stdout_lines:
- 'Order Stored and Event Published'
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/orders -H 'Content-Type: application/json' -d '{ "item": "the mars volta EP", "amount": 1 }'
```

<!-- END_STEP -->


If you check the `producer-app` logs you should see the following lines:

```bash
...
Storing Order: Order{id='null', item='the mars volta EP', amount=1}
Publishing Order Event: Order{id='d4f8ea15-b774-441e-bcd2-7a4208a80bec', item='the mars volta EP', amount=1}

```

If you check the `consumer-app` logs you should see the following lines, showing that the message
published by the `producer-app` was correctly consumed by the `consumer-app`:

```bash
Order Event Received: Order{id='d4f8ea15-b774-441e-bcd2-7a4208a80bec', item='the mars volta EP', amount=1}
```

Next, you can create a new customer to trigger the customer's tracking workflow:

<!-- STEP
name: Start Customer Workflow
match_order: none
output_match_mode: substring
expected_stdout_lines:
- 'New Workflow Instance created for Customer'
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/customers -H 'Content-Type: application/json' -d '{ "customerName": "salaboy" }'
```

<!-- END_STEP -->


A new Workflow Instance was created to track the customers interactions. Now, the workflow instance
is waiting for the customer to request a follow-up.

You should see in the `producer-app` logs:

```bash
Workflow instance <Workflow Instance Id> started
Let's register the customer: salaboy
Customer: salaboy registered.
Let's wait for the customer: salaboy to request a follow up.
```

Send an event simulating the customer request for a follow-up:

<!-- STEP
name: Emit Customer Follow-up event
match_order: none
output_match_mode: substring
expected_stdout_lines:
- 'Customer Follow-up requested'
background: true
sleep: 1
timeout_seconds: 5
-->
<!-- Timeout for above service must be more than sleep + timeout for the client-->

```sh
curl -X POST localhost:8080/customers/followup -H 'Content-Type: application/json' -d '{ "customerName": "salaboy" }'
```

<!-- END_STEP -->

In the `producer-app` logs you should see that the workflow instance id moved forward to the Customer Follow Up activity:

```bash
Customer follow-up requested: salaboy
Let's book a follow up for the customer: salaboy
Customer: salaboy follow-up done.
Congratulations the customer: salaboy is happy!
```

## Running on Kubernetes

You can run the same example on a Kubernetes cluster. [Check the Kubernetes tutorial here](kubernetes/README.md).
Loading
Loading