Hello. RSocket is supported in Spring Boot 2.2, so I tried it.
--A binary protocol designed for communication between microservices. --Reactive Socket. Supports Reactive Streams. --TCP, WebSocket, Aeron, and HTTP / 2 streams can be used as transport protocols. --The following four interaction models can be used. --request-response (1 request, 1 response) --request-stream (1 request, multiple responses) --fire-and-forget (1 request, No response --channel (multiple requests, multiple responses) --Development language independent. Java, JavaScriot, GO, .NET, etc. --Official support from Spring 5.2 and Spring Boot 2.2. --Official site: RSocket official site
vs REST --RSocket has less communication overhead than REST. --If you want to realize the above four interactions with REST, you need a mechanism such as polling.
vs gRPC --gRPC does not support communication with the browser. If you want to communicate with your browser, you need a proxy to convert to REST.
For details, refer to the following site.
Now that I have a rough idea of the outline while looking at the official website, I will create a simple application. Since RSocket is a communication protocol between services,
--Request sender (rsocket-client) --The side that receives the request and returns the response (rsocket-backend)
You need two. Create both rsocket-client and rsocket-backend as servers, and for the time being, try communication between the servers. The sequence diagram of the application is as follows.
I wanted to access it in a browser and see the results, so rsocket-client is an application with an HTTP endpoint. 1-4 are "request-response" interactions and 5-9 are "request-stream" interactions. I used SSE (Server-Sent Events) because I wanted the response of 9 to be returned at any time.
Create rsocket-client and rsocket-backend as subprojects respectively. Also, I didn't want to define the data model (DTO) used for request and response in each project, so I will also create this in the subproject (rsocket-model).
|- build.gradle
|- rsocket-model/
| |- build.gradle
| :
|- rsocket-client/
| |- build.gradle
| :
└ rsocket-backend/
|- build.gradle
:
The common build.gradle looks like this.
Add spring-boot-starter-rsocket
and spring-boot-starter-webflux
to dependency.
build.gradle
buildscript {
repositories {
mavenCentral()
maven { url "https://plugins.gradle.org/m2/" }
}
dependencies {
classpath "org.springframework.boot:spring-boot-gradle-plugin:2.2.2.RELEASE"
classpath "io.spring.gradle:dependency-management-plugin:1.0.8.RELEASE"
}
}
allprojects {
repositories {
mavenCentral()
}
}
subprojects {
group = 'sandbox'
version = '0.0.1-SNAPSHOT'
apply plugin: "java"
apply plugin: "java-library"
apply plugin: "org.springframework.boot"
apply plugin: "io.spring.dependency-management"
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
dependencyManagement {
dependencies {
dependency "org.springframework.boot:spring-boot-starter-rsocket:2.2.2.RELEASE"
dependency "org.springframework.boot:spring-boot-starter-webflux:2.2.2.RELEASE"
dependency "org.springframework.boot:spring-boot-devtools:2.2.2.RELEASE"
}
}
}
rsocket-model build.gradle of rsocket-model.
build.gradle
project(":rsocket-model") {
dependencies {
compileOnly("org.projectlombok:lombok")
annotationProcessor("org.projectlombok:lombok")
}
bootJar {
enabled = false
}
jar {
enabled = true
}
}
Definition of the DTO of the request. It seems that Jackson is used to encode and decode Java objects, so don't forget to create a default constructor.
RequestData.java
package sandbox.rsocket;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@NoArgsConstructor
@AllArgsConstructor
@Data
public class RequestData {
String message;
}
Definition of DTO for the response.
ResponseData.java
package sandbox.rsocket;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@NoArgsConstructor
@AllArgsConstructor
@Data
public class ResponseData {
String message;
}
rsocket-bankend rsocket-bankend build.gradle.
rsocket-bankend/build.gradle
project(":rsocket-backend") {
dependencies {
implementation project(':rsocket-model')
implementation 'org.springframework.boot:spring-boot-starter-rsocket'
implementation 'org.springframework.boot:spring-boot-starter-webflux'
implementation 'org.springframework.boot:spring-boot-devtools'
compileOnly("org.projectlombok:lombok")
annotationProcessor("org.projectlombok:lombok")
}
}
Specify the port number (7000) used by RSocket in application.yml. You can also specify the transport protocol from YAML. The definition of this area can be found in the Spring Boot documentation. 8.2. RSocket server Auto-configuration
application.yml
spring:
rsocket:
server:
port: 7000
# Remove commented out if enable RSocket over websocket. (Using tcp as default.)
# See the link for the details. https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/html/spring-boot-features.html#boot-features-rsocket-strategies-auto-configuration
# mapping-path: /rsocket
# transport: websocket
RSocket server controller definition.
Set the RSocket endpoint name with the @MessageMapping
annotation.
Since getMono
returns a single data, Mono
and getFlux
return multiple data (stream), so the response is stored in Flux
.
RSocketServerController.java
package sandbox.rsocket;
import java.time.Duration;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.stereotype.Controller;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@Slf4j
@AllArgsConstructor
@Controller
public class RSocketServerController {
/**
* Get response data with mono.
*/
@MessageMapping("getMono")
public Mono<ResponseData> getMono(RequestData requestData) {
log.info("Calling getMono method. request={}", requestData);
return Mono.just(new ResponseData(requestData.getMessage()));
}
/**
* Get response data with flux.
* Responds one of the response data every seconds.
*/
@MessageMapping("getFlux")
public Flux<ResponseData> getFlux(RequestData requestData) {
log.info("Calling getFlux method. request={}", requestData);
final List<ResponseData> list =
IntStream.rangeClosed(1, 10)
.boxed()
.map(i -> new ResponseData(requestData.getMessage() + '_' + i))
.collect(Collectors.toList());
return Flux.fromIterable(list)
.delayElements(Duration.ofSeconds(1));
}
}
RSocket-client
build.gradle for rsocket-client.
rsocket-client/build.gradle
project(":rsocket-client") {
dependencies {
implementation project(':rsocket-model')
implementation 'org.springframework.boot:spring-boot-starter-rsocket'
implementation 'org.springframework.boot:spring-boot-starter-webflux'
implementation 'org.springframework.boot:spring-boot-devtools'
compileOnly("org.projectlombok:lombok")
annotationProcessor("org.projectlombok:lombok")
}
}
In application.yml, specify the port number (8081) of the HTTP endpoint. By the way, if you set the log level on the client side to DEBUG, you can output the frame information of RSocket to the console.
application.yml
server:
port: 8081
# Remove commented out if u want to see RSocket frame on console log.
# logging:
# level:
# root: DEBUG
The following is the DEBUG log output to the console when requesting getMono
. You can see a log like that!
2020-01-08 23:15:00.853 DEBUG 6776 --- [ctor-http-nio-2] o.s.http.codec.cbor.Jackson2CborEncoder : Encoding [RequestData(message=test)]
2020-01-08 23:15:00.879 DEBUG 6776 --- [actor-tcp-nio-1] io.rsocket.FrameLogger : sending ->
Frame => Stream ID: 1 Type: REQUEST_RESPONSE Flags: 0b100000000 Length: 36
Metadata:
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| fe 00 00 08 07 67 65 74 4d 6f 6e 6f |.....getMono |
+--------+-------------------------------------------------+----------------+
Data:
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| bf 67 6d 65 73 73 61 67 65 64 74 65 73 74 ff |.gmessagedtest. |
+--------+-------------------------------------------------+----------------+
2020-01-08 23:15:01.015 DEBUG 6776 --- [actor-tcp-nio-1] io.rsocket.FrameLogger : receiving ->
Frame => Stream ID: 1 Type: NEXT_COMPLETE Flags: 0b1100000 Length: 21
Data:
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| bf 67 6d 65 73 73 61 67 65 64 74 65 73 74 ff |.gmessagedtest. |
+--------+-------------------------------------------------+----------------+
2020-01-08 23:15:01.023 DEBUG 6776 --- [actor-tcp-nio-1] o.s.http.codec.cbor.Jackson2CborDecoder : Decoded [ResponseData(message=test)]
2020-01-08 23:15:01.023 DEBUG 6776 --- [actor-tcp-nio-1] o.s.http.codec.json.Jackson2JsonEncoder : [e539e702] Encoding [ResponseData(message=test)]
Definition of Configuration for the client. Set the host name (localhost) and port number (7000) of the RSocket server in RSocketRequester
. The Builder class is provided, so you can write it in a few lines.
ClientConfiguration.java
package sandbox.rsocket;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.rsocket.RSocketRequester;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@AllArgsConstructor
@Configuration
public class ClientConfiguration {
private final RSocketRequester.Builder builder;
@Bean
public RSocketRequester rSocketRequester() {
return builder.connectTcp("localhost", 7000)
.doOnNext(socket -> log.info("Connected to RSocket."))
.block();
}
}
RSocket client service class definition. The endpoint name of the RSocket server is set here.
RSocketClientService.java
package sandbox.rsocket;
import org.springframework.messaging.rsocket.RSocketRequester;
import org.springframework.stereotype.Service;
import lombok.AllArgsConstructor;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@AllArgsConstructor
@Service
public class RSocketClientService {
private final RSocketRequester rSocketRequester;
public Mono<ResponseData> getMono(RequestData data) {
return rSocketRequester.route("getMono")
.data(data)
.retrieveMono(ResponseData.class);
}
public Flux<ResponseData> getFlux(RequestData data) {
return rSocketRequester.route("getFlux")
.data(data)
.retrieveFlux(ResponseData.class);
}
}
Definition of HTTP endpoint for RSocket client.
RSocketClientController.java
package sandbox.rsocket;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import lombok.AllArgsConstructor;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@AllArgsConstructor
@RestController
public class RSocketClientController {
private final RSocketClientService clientService;
/**
* Get response mono data.
*/
@GetMapping("/mono")
public Mono<ResponseData> mono(@RequestParam String message) {
return clientService.getMono(new RequestData(message));
}
/**
* Get response flux data with server sent events.
*/
@GetMapping(produces = MediaType.TEXT_EVENT_STREAM_VALUE, value = "/flux")
public Flux<ResponseData> flux(@RequestParam String message) {
return clientService.getFlux(new RequestData(message));
}
}
BootRun rsocket-bankend and rsocket-client respectively to access the HTTP endpoint.
The following is the result when accessing http: // localhost: 8081 / flux
. Data is being responded every second!
--I created a simple application using Spring Boot and RSocket and tried communication between servers. ――It was easy to create with very little code, but there are many black box parts, and detailed understanding is required when actually using it. I thought. (Small feeling) ――Although it is not written in this article, I also tried communication with the browser using "RSocket over WebSocket". As a web app developer, I'm very happy to have a way to replace REST. (This is also a small feeling) --The source code created this time is saved in GitHub: RSocket sandbox. I would appreciate it if you could refer to it.
Recommended Posts