[JAVA] RSocket is supported in Spring Boot 2.2, so give it a try

Hello. RSocket is supported in Spring Boot 2.2, so I tried it.

What is RSocket?

--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

Comparison with other protocols

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.

I will try it immediately

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.

スクリーンショット 2020-01-08 22.20.47.png

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.

Project structure

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!

Flux_demo.gif

Summary

--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.

reference

Recommended Posts

RSocket is supported in Spring Boot 2.2, so give it a try
What is @Autowired in Spring boot?
What is a Spring Boot .original file?
Static analysis is finally coming to Ruby, so I'll give it a try. [RBS] [Type Prof] [Steep]
Run a Spring Boot project in VS Code
How to add a classpath in Spring Boot
Java tips-Create a Spring Boot project in Gradle
When internationalizing is supported by Spring Boot, a specific locale is not translated and I am addicted to it
Until you create a Spring Boot project in Intellij and push it to Github
How to create a Spring Boot project in IntelliJ
Try gRPC in Spring Boot & Spring Cloud project (Mac OS)
Set context-param in Spring Boot
Spring Boot 2 multi-project in Gradle
Major changes in Spring Boot 1.5
NoHttpResponseException in Spring Boot + WireMock
Try using Spring Boot Security
Try Spring Boot on Mac
Get a proxy instance of the component itself in Spring Boot
Part 1: Try using OAuth 2.0 Login supported by Spring Security 5 with Spring Boot
Fitted in Spring Boot using a bean definition file named application.xml
Let's write a test code for login function in Spring Boot
[Spring Boot] Until @Autowired is run in the test class [JUnit5]
Spring Boot Hello World in Eclipse
Spring Boot application development in Eclipse
Write test code in Spring Boot
What is a snippet in programming?
Implement REST API in Spring Boot
Event processing is performed in Spring.
Implement Spring Boot application in Gradle
Try running Spring Boot on Kubernetes
A memo that touched Spring Boot
Thymeleaf usage notes in Spring Boot
Spring.messages.fallback-to-system-locale: false is required to default message.properties for i18n support in Spring boot
Create a Spring Boot project in intellij and exit immediately after launching
A story about a Spring Boot project written in Java that supports Kotlin
Autowired fields in a class that inherits TextWebSocketHandler in Spring Boot become NULL
Memo: [Java] If a file is in the monitored directory, process it.
Automatically deploy a web application developed in Java using Jenkins [Spring Boot application]
When I defined a session scope bean in Spring Boot, it behaved strangely and needed to be adjusted.
A super beginner has completed the Spring introductory book, so I will summarize it in my own way