Skip to content

Commit 4ff319c

Browse files
authored
Merge branch 'master' into actor_ttl
2 parents a571359 + 5dbeafc commit 4ff319c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+2047
-17
lines changed

.github/scripts/update_sdk_version.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,7 @@ mvn versions:set -DnewVersion=$DAPR_JAVA_SDK_ALPHA_VERSION -f testcontainers-dap
2727
# dapr-spring
2828
mvn versions:set -DnewVersion=$DAPR_JAVA_SDK_ALPHA_VERSION -f dapr-spring/pom.xml
2929

30+
# spring-boot-examples
31+
mvn versions:set -DnewVersion=$DAPR_JAVA_SDK_ALPHA_VERSION -f spring-boot-examples/pom.xml
32+
3033
git clean -f

.github/workflows/validate.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,3 +164,7 @@ jobs:
164164
working-directory: ./examples
165165
run: |
166166
mm.py ./src/main/java/io/dapr/examples/pubsub/stream/README.md
167+
- name: Validate Spring Boot examples
168+
working-directory: ./spring-boot-examples
169+
run: |
170+
mm.py README.md

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ This is the Dapr SDK for Java, including the following features:
99
* Binding
1010
* State Store
1111
* Actors
12+
* Workflows
1213

1314
## Getting Started
1415

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

116+
### Running Spring Boot examples
117+
118+
The Spring Boot integration for Dapr use [Testcontainers](https://testcontainers.com) to set up a local environment development flow that doesn't
119+
require the use of the `dapr` CLI and it integrates with the Spring Boot programming model.
120+
121+
You can find a [step-by-step tutorial showing this integration here](./spring-boot-examples/README.md).
122+
115123
### API Documentation
116124

117125
Please, refer to our [Javadoc](https://dapr.github.io/java-sdk/) website.

pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,7 @@
337337
<module>sdk-springboot</module>
338338
<module>dapr-spring</module>
339339
<module>examples</module>
340+
<module>spring-boot-examples</module>
340341
<!-- We are following test containers artifact convention on purpose, don't rename -->
341342
<module>testcontainers-dapr</module>
342343
</modules>

sdk-tests/src/test/java/io/dapr/it/testcontainers/DaprContainerIT.java

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,10 @@ public class DaprContainerIT {
6363

6464
@Container
6565
private static final DaprContainer DAPR_CONTAINER = new DaprContainer(IMAGE_TAG)
66-
.withAppName("dapr-app")
67-
.withAppPort(8081)
68-
.withAppChannelAddress("host.testcontainers.internal");
66+
.withAppName("dapr-app")
67+
.withAppPort(8081)
68+
.withAppHealthCheckPath("/actuator/health")
69+
.withAppChannelAddress("host.testcontainers.internal");
6970

7071
/**
7172
* Sets the Dapr properties for the test.
@@ -77,32 +78,35 @@ public void setDaprProperties() {
7778
}
7879

7980
private void configStub() {
81+
stubFor(any(urlMatching("/actuator/health"))
82+
.willReturn(aResponse().withBody("[]").withStatus(200)));
83+
8084
stubFor(any(urlMatching("/dapr/subscribe"))
81-
.willReturn(aResponse().withBody("[]").withStatus(200)));
85+
.willReturn(aResponse().withBody("[]").withStatus(200)));
8286

8387
stubFor(get(urlMatching("/dapr/config"))
84-
.willReturn(aResponse().withBody("[]").withStatus(200)));
88+
.willReturn(aResponse().withBody("[]").withStatus(200)));
8589

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

8993
// create a stub
9094
stubFor(post(urlEqualTo("/events"))
91-
.willReturn(aResponse().withBody("event received!").withStatus(200)));
95+
.willReturn(aResponse().withBody("event received!").withStatus(200)));
9296

9397
configureFor("localhost", 8081);
9498
}
9599

96100
@Test
97101
public void testDaprContainerDefaults() {
98102
assertEquals(2,
99-
DAPR_CONTAINER.getComponents().size(),
100-
"The pubsub and kvstore component should be configured by default"
103+
DAPR_CONTAINER.getComponents().size(),
104+
"The pubsub and kvstore component should be configured by default"
101105
);
102106
assertEquals(
103-
1,
104-
DAPR_CONTAINER.getSubscriptions().size(),
105-
"A subscription should be configured by default if none is provided"
107+
1,
108+
DAPR_CONTAINER.getSubscriptions().size(),
109+
"A subscription should be configured by default if none is provided"
106110
);
107111
}
108112

@@ -129,10 +133,10 @@ public void testPlacement() throws Exception {
129133
Thread.sleep(1000);
130134

131135
OkHttpClient okHttpClient = new OkHttpClient.Builder()
132-
.build();
136+
.build();
133137
Request request = new Request.Builder()
134-
.url(DAPR_CONTAINER.getHttpEndpoint() + "/v1.0/metadata")
135-
.build();
138+
.url(DAPR_CONTAINER.getHttpEndpoint() + "/v1.0/metadata")
139+
.build();
136140

137141
try (Response response = okHttpClient.newCall(request).execute()) {
138142
if (response.isSuccessful() && response.body() != null) {
@@ -158,7 +162,7 @@ public void testPubSub() throws Exception {
158162

159163
private DaprClientBuilder createDaprClientBuilder() {
160164
return new DaprClientBuilder()
161-
.withPropertyOverride(Properties.HTTP_ENDPOINT, DAPR_CONTAINER.getHttpEndpoint())
162-
.withPropertyOverride(Properties.GRPC_ENDPOINT, DAPR_CONTAINER.getGrpcEndpoint());
165+
.withPropertyOverride(Properties.HTTP_ENDPOINT, DAPR_CONTAINER.getHttpEndpoint())
166+
.withPropertyOverride(Properties.GRPC_ENDPOINT, DAPR_CONTAINER.getGrpcEndpoint());
163167
}
164168
}

spring-boot-examples/README.md

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
# Dapr Spring Boot and Testcontainers integration Example
2+
3+
This example consists of two applications:
4+
- Producer App:
5+
- Publish messages using a Spring Messaging approach
6+
- Store and retrieve information using Spring Data CrudRepository
7+
- Implements a Workflow with Dapr Workflows
8+
- Consumer App:
9+
- Subscribe to messages
10+
11+
## Running these examples from source code
12+
13+
To run these examples you will need:
14+
- Java SDK
15+
- Maven
16+
- Docker or a container runtime such as Podman
17+
18+
From the `spring-boot-examples/` directory you can start each service using the test configuration that uses
19+
[Testcontainers](https://testcontainers.com) to boostrap [Dapr](https://dapr.io) by running the following command:
20+
21+
<!-- STEP
22+
name: Run Demo Producer Service
23+
match_order: none
24+
output_match_mode: substring
25+
expected_stdout_lines:
26+
- 'Started ProducerApplication'
27+
background: true
28+
expected_return_code: 143
29+
sleep: 30
30+
timeout_seconds: 45
31+
-->
32+
<!-- Timeout for above service must be more than sleep + timeout for the client-->
33+
34+
```sh
35+
cd producer-app/
36+
../../mvnw -Dspring-boot.run.arguments="--reuse=true" spring-boot:test-run
37+
```
38+
39+
<!-- END_STEP -->
40+
41+
This will start the `producer-app` with Dapr services and the infrastructure needed by the application to run,
42+
in this case RabbitMQ and PostgreSQL. The `producer-app` starts on port `8080` by default.
43+
44+
The `-Dspring-boot.run.arguments="--reuse=true"` flag helps the application to connect to an existing shared
45+
infrastructure if it already exists. For development purposes, and to connect both applications we will set the flag
46+
in both. For more details check the `DaprTestContainersConfig.java` classes in both, the `producer-app` and the `consumer-app`.
47+
48+
Then run in a different terminal:
49+
50+
<!-- STEP
51+
name: Run Demo Consumer Service
52+
match_order: none
53+
output_match_mode: substring
54+
expected_stdout_lines:
55+
- 'Started ConsumerApplication'
56+
background: true
57+
expected_return_code: 143
58+
sleep: 30
59+
timeout_seconds: 45
60+
-->
61+
<!-- Timeout for above service must be more than sleep + timeout for the client-->
62+
63+
```sh
64+
cd consumer-app/
65+
../../mvnw -Dspring-boot.run.arguments="--reuse=true" spring-boot:test-run
66+
```
67+
68+
<!-- END_STEP -->
69+
The `consumer-app` starts in port `8081` by default.
70+
71+
## Interacting with the applications
72+
73+
Now that both applications are up you can place an order by sending a POST request to `:8080/orders/`
74+
You can use `curl` to send a POST request to the `producer-app`:
75+
76+
77+
<!-- STEP
78+
name: Send POST request to Producer App
79+
match_order: none
80+
output_match_mode: substring
81+
expected_stdout_lines:
82+
- 'Order Stored and Event Published'
83+
background: true
84+
sleep: 1
85+
timeout_seconds: 2
86+
-->
87+
<!-- Timeout for above service must be more than sleep + timeout for the client-->
88+
89+
```sh
90+
curl -X POST localhost:8080/orders -H 'Content-Type: application/json' -d '{ "item": "the mars volta EP", "amount": 1 }'
91+
```
92+
93+
<!-- END_STEP -->
94+
95+
96+
If you check the `producer-app` logs you should see the following lines:
97+
98+
```bash
99+
...
100+
Storing Order: Order{id='null', item='the mars volta EP', amount=1}
101+
Publishing Order Event: Order{id='d4f8ea15-b774-441e-bcd2-7a4208a80bec', item='the mars volta EP', amount=1}
102+
103+
```
104+
105+
If you check the `consumer-app` logs you should see the following lines, showing that the message
106+
published by the `producer-app` was correctly consumed by the `consumer-app`:
107+
108+
```bash
109+
Order Event Received: Order{id='d4f8ea15-b774-441e-bcd2-7a4208a80bec', item='the mars volta EP', amount=1}
110+
```
111+
112+
Next, you can create a new customer to trigger the customer's tracking workflow:
113+
114+
<!-- STEP
115+
name: Start Customer Workflow
116+
match_order: none
117+
output_match_mode: substring
118+
expected_stdout_lines:
119+
- 'New Workflow Instance created for Customer'
120+
background: true
121+
sleep: 1
122+
timeout_seconds: 2
123+
-->
124+
<!-- Timeout for above service must be more than sleep + timeout for the client-->
125+
126+
```sh
127+
curl -X POST localhost:8080/customers -H 'Content-Type: application/json' -d '{ "customerName": "salaboy" }'
128+
```
129+
130+
<!-- END_STEP -->
131+
132+
133+
A new Workflow Instance was created to track the customers interactions. Now, the workflow instance
134+
is waiting for the customer to request a follow-up.
135+
136+
You should see in the `producer-app` logs:
137+
138+
```bash
139+
Workflow instance <Workflow Instance Id> started
140+
Let's register the customer: salaboy
141+
Customer: salaboy registered.
142+
Let's wait for the customer: salaboy to request a follow up.
143+
```
144+
145+
Send an event simulating the customer request for a follow-up:
146+
147+
<!-- STEP
148+
name: Emit Customer Follow-up event
149+
match_order: none
150+
output_match_mode: substring
151+
expected_stdout_lines:
152+
- 'Customer Follow-up requested'
153+
background: true
154+
sleep: 1
155+
timeout_seconds: 5
156+
-->
157+
<!-- Timeout for above service must be more than sleep + timeout for the client-->
158+
159+
```sh
160+
curl -X POST localhost:8080/customers/followup -H 'Content-Type: application/json' -d '{ "customerName": "salaboy" }'
161+
```
162+
163+
<!-- END_STEP -->
164+
165+
In the `producer-app` logs you should see that the workflow instance id moved forward to the Customer Follow Up activity:
166+
167+
```bash
168+
Customer follow-up requested: salaboy
169+
Let's book a follow up for the customer: salaboy
170+
Customer: salaboy follow-up done.
171+
Congratulations the customer: salaboy is happy!
172+
```
173+
174+
## Running on Kubernetes
175+
176+
You can run the same example on a Kubernetes cluster. [Check the Kubernetes tutorial here](kubernetes/README.md).

0 commit comments

Comments
 (0)