Skip to content

Commit 8b38273

Browse files
author
Dave Syer
authored
Add support for RSockets with Spring (#349)
* Add support for RSockets with Spring Also generically can support structured events with any Spring API that works with Encoder and Decoder. There's a sample for the RSocket case with a simple request-response echo server. Signed-off-by: Dave Syer <[email protected]> * Use supported mime types from format provider Signed-off-by: Dave Syer <[email protected]>
1 parent f05418c commit 8b38273

File tree

14 files changed

+572
-3
lines changed

14 files changed

+572
-3
lines changed

docs/spring.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ nav_order: 5
88
[![Javadocs](http://www.javadoc.io/badge/io.cloudevents/cloudevents-spring.svg?color=green)](http://www.javadoc.io/doc/io.cloudevents/cloudevents-spring)
99

1010
This module provides the integration of `CloudEvent` with different Spring APIs,
11-
like MVC, WebFlux and Messaging
11+
like MVC, WebFlux, RSocket and Messaging
1212

1313
For Maven based projects, use the following dependency:
1414

@@ -43,3 +43,6 @@ Check out the samples:
4343
- [spring-reactive](https://github.com/cloudevents/sdk-java/tree/master/examples/spring-reactive)
4444
shows how to receive and send CloudEvents through HTTP using Spring Boot and
4545
Webflux.
46+
47+
- [spring-rsocket](https://github.com/cloudevents/sdk-java/tree/master/examples/spring-rsocket)
48+
shows how to receive and send CloudEvents through RSocket using Spring Boot.

examples/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
<module>restful-ws-spring-boot</module>
2727
<module>amqp-proton</module>
2828
<module>spring-reactive</module>
29+
<module>spring-rsocket</module>
2930
</modules>
3031

3132
</project>

examples/spring-reactive/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
<artifactId>cloudevents-spring-reactive-example</artifactId>
1313

1414
<properties>
15-
<spring-boot.version>2.4.0</spring-boot.version>
15+
<spring-boot.version>2.4.3</spring-boot.version>
1616
</properties>
1717

1818
<dependencyManagement>

examples/spring-rsocket/README.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Spring RSockets + CloudEvents sample
2+
3+
## Build
4+
5+
```shell
6+
mvn package
7+
```
8+
9+
## Start Server
10+
11+
```shell
12+
mvn spring-boot:run
13+
```
14+
15+
You can try sending a request using [rsc](https://github.com/making/rsc), and it echos back a cloud event the same body and with new `ce-*` headers:
16+
17+
```shell
18+
rsc --request --dataMimeType=application/cloudevents+json --route=event \
19+
--data='{"data": {"value": "Foo"},
20+
"id": "1",
21+
"source": "cloud-event-example",
22+
"type": "my.application.Foo",
23+
"specversion": "1.0"}' \
24+
--debug tcp://localhost:7000
25+
```
26+
27+
The `event` endpoint is implemented like this (the request and response are modelled directly as a `CloudEvent`):
28+
29+
```java
30+
@MessageMapping("event")
31+
public Mono<CloudEvent> event(@RequestBody Mono<CloudEvent> body) {
32+
return ...;
33+
}
34+
```
35+
36+
and to make that work we need to install the codecs:
37+
38+
```java
39+
@Bean
40+
@Order(-1)
41+
public RSocketStrategiesCustomizer cloudEventsCustomizer() {
42+
return new RSocketStrategiesCustomizer() {
43+
@Override
44+
public void customize(Builder strategies) {
45+
strategies.encoder(new CloudEventEncoder());
46+
strategies.decoder(new CloudEventDecoder());
47+
}
48+
};
49+
50+
}
51+
```

examples/spring-rsocket/pom.xml

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<parent>
6+
<artifactId>cloudevents-examples</artifactId>
7+
<groupId>io.cloudevents</groupId>
8+
<version>2.1.0-SNAPSHOT</version>
9+
</parent>
10+
<modelVersion>4.0.0</modelVersion>
11+
12+
<artifactId>cloudevents-spring-rsocket-example</artifactId>
13+
14+
<properties>
15+
<spring-boot.version>2.4.3</spring-boot.version>
16+
</properties>
17+
18+
<dependencyManagement>
19+
<dependencies>
20+
<dependency>
21+
<groupId>org.springframework.boot</groupId>
22+
<artifactId>spring-boot-dependencies</artifactId>
23+
<version>${spring-boot.version}</version>
24+
<type>pom</type>
25+
<scope>import</scope>
26+
</dependency>
27+
</dependencies>
28+
</dependencyManagement>
29+
30+
<dependencies>
31+
<dependency>
32+
<groupId>org.springframework.boot</groupId>
33+
<artifactId>spring-boot-starter-rsocket</artifactId>
34+
</dependency>
35+
<dependency>
36+
<groupId>io.cloudevents</groupId>
37+
<artifactId>cloudevents-spring</artifactId>
38+
<version>${project.version}</version>
39+
</dependency>
40+
<dependency>
41+
<groupId>io.cloudevents</groupId>
42+
<artifactId>cloudevents-json-jackson</artifactId>
43+
<version>${project.version}</version>
44+
</dependency>
45+
<dependency>
46+
<groupId>org.springframework.boot</groupId>
47+
<artifactId>spring-boot-starter-test</artifactId>
48+
<scope>test</scope>
49+
</dependency>
50+
</dependencies>
51+
52+
<build>
53+
<plugins>
54+
<plugin>
55+
<groupId>org.springframework.boot</groupId>
56+
<artifactId>spring-boot-maven-plugin</artifactId>
57+
<version>${spring-boot.version}</version>
58+
</plugin>
59+
</plugins>
60+
</build>
61+
62+
</project>
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package io.cloudevents.examples.spring;
2+
3+
import java.net.URI;
4+
import java.util.UUID;
5+
6+
import io.cloudevents.CloudEvent;
7+
import io.cloudevents.core.builder.CloudEventBuilder;
8+
import io.cloudevents.spring.codec.CloudEventDecoder;
9+
import io.cloudevents.spring.codec.CloudEventEncoder;
10+
import reactor.core.publisher.Mono;
11+
12+
import org.springframework.boot.SpringApplication;
13+
import org.springframework.boot.autoconfigure.SpringBootApplication;
14+
import org.springframework.boot.rsocket.messaging.RSocketStrategiesCustomizer;
15+
import org.springframework.context.annotation.Bean;
16+
import org.springframework.core.annotation.Order;
17+
import org.springframework.messaging.handler.annotation.MessageMapping;
18+
import org.springframework.messaging.rsocket.RSocketStrategies.Builder;
19+
import org.springframework.stereotype.Controller;
20+
import org.springframework.web.bind.annotation.RequestBody;
21+
22+
@SpringBootApplication
23+
@Controller
24+
public class DemoApplication {
25+
26+
public static void main(String[] args) throws Exception {
27+
SpringApplication.run(DemoApplication.class, args);
28+
}
29+
30+
@MessageMapping("event")
31+
// Use CloudEvent API and manual type conversion of request and response body
32+
public Mono<CloudEvent> event(@RequestBody Mono<CloudEvent> body) {
33+
return body.map(event -> CloudEventBuilder.from(event) //
34+
.withId(UUID.randomUUID().toString()) //
35+
.withSource(URI.create("https://spring.io/foos")) //
36+
.withType("io.spring.event.Foo") //
37+
.withData(event.getData().toBytes()) //
38+
.build());
39+
}
40+
41+
@Bean
42+
@Order(-1)
43+
public RSocketStrategiesCustomizer cloudEventsCustomizer() {
44+
return new RSocketStrategiesCustomizer() {
45+
@Override
46+
public void customize(Builder strategies) {
47+
strategies.encoder(new CloudEventEncoder());
48+
strategies.decoder(new CloudEventDecoder());
49+
}
50+
};
51+
52+
}
53+
54+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright 2019-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.cloudevents.examples.spring;
17+
18+
class Foo {
19+
20+
private String value;
21+
22+
public Foo() {
23+
}
24+
25+
public Foo(String value) {
26+
this.value = value;
27+
}
28+
29+
public String getValue() {
30+
return this.value;
31+
}
32+
33+
public void setValue(String value) {
34+
this.value = value;
35+
}
36+
37+
@Override
38+
public String toString() {
39+
return "Foo [value=" + this.value + "]";
40+
}
41+
42+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
spring.rsocket.server.port=7000
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package io.cloudevents.examples.spring;
2+
3+
import java.net.URI;
4+
import java.util.UUID;
5+
6+
import com.fasterxml.jackson.databind.ObjectMapper;
7+
import io.cloudevents.CloudEvent;
8+
import io.cloudevents.core.builder.CloudEventBuilder;
9+
import io.cloudevents.core.data.PojoCloudEventData;
10+
import org.junit.jupiter.api.BeforeEach;
11+
import org.junit.jupiter.api.Test;
12+
13+
import org.springframework.beans.factory.annotation.Autowired;
14+
import org.springframework.boot.test.context.SpringBootTest;
15+
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
16+
import org.springframework.messaging.rsocket.RSocketRequester;
17+
import org.springframework.util.MimeType;
18+
19+
import static org.assertj.core.api.Assertions.assertThat;
20+
21+
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
22+
public class DemoApplicationTests {
23+
24+
@Autowired
25+
private RSocketRequester.Builder builder;
26+
27+
@Autowired
28+
private ObjectMapper mapper;
29+
30+
private RSocketRequester rsocketRequester;
31+
32+
@BeforeEach
33+
public void init() {
34+
String host = "localhost";
35+
int port = 7000;
36+
rsocketRequester = builder
37+
.dataMimeType(MimeType.valueOf("application/cloudevents+json"))
38+
.tcp(host, port);
39+
}
40+
41+
@Test
42+
void echoWithCorrectHeaders() {
43+
CloudEvent result = rsocketRequester.route("event")
44+
.data(CloudEventBuilder.v1()
45+
.withDataContentType("application/cloudevents+json")
46+
.withId(UUID.randomUUID().toString()) //
47+
.withSource(URI.create("https://spring.io/foos")) //
48+
.withType("io.spring.event.Foo") //
49+
.withData(PojoCloudEventData.wrap(new Foo("Dave"),
50+
mapper::writeValueAsBytes))
51+
.build())
52+
.retrieveMono(CloudEvent.class).block();
53+
54+
assertThat(new String(result.getData().toBytes()))
55+
.isEqualTo("{\"value\":\"Dave\"}");
56+
57+
}
58+
59+
}

spring/pom.xml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232

3333
<properties>
3434
<module-name>io.cloudevents.spring</module-name>
35-
<spring-boot.version>2.4.0</spring-boot.version>
35+
<spring-boot.version>2.4.3</spring-boot.version>
3636
</properties>
3737

3838
<dependencyManagement>
@@ -79,6 +79,13 @@
7979
<artifactId>tomcat-embed-core</artifactId>
8080
<optional>true</optional>
8181
</dependency>
82+
<dependency>
83+
<groupId>com.google.code.findbugs</groupId>
84+
<artifactId>jsr305</artifactId>
85+
<version>3.0.2</version>
86+
<scope>provided</scope>
87+
<optional>true</optional>
88+
</dependency>
8289

8390
<!-- Test deps -->
8491
<dependency>

0 commit comments

Comments
 (0)