스프링부트 WebFlux 스트리밍
간단하게 스프링 부트 Reactive 예제를 만들어 보고 어떤 느낌? 서비스? 인지 알아보도록 한다.
스프링부트에 리액티브 방식(기술)이 추가된 것을 설명하려면 많은 사전지식과 근 10년 내외의 IT서비스 상황을 이해해야 하므로 글이 너무 길어질 수 있으므로 우선은 모노리틱 아키텍처의 한계를 극복하고 MSA(마이크로 분산서비스)에 적합한 대안? 또는 기술 정도로만 이해하고 있자.
솔직히 내 생각에는 리액티브 프로그램을 제대로 이해하려면 IT전반의 배경지식도 필요하지만, 블럭킹과 논블럭킹, 동기와 비동기, 스레드와 프로세스까지 어느 정도 깊이 있게 이해를 해야만 제대로 된 이 기술을 익힐 수 있고 왜 익혀야 하는지 동기까지 가질 수 있다.
모두들 러닝 곡선이 가파르다고 하는데 그 이유가 여기에 있다.
전통적인 HTTP 통신 방식은 클라이언트가 서버에 요청을 하고 응답을 받는다. 여기서 두 개체 간 통신은 끝이다. 하지만 리액티브는 다르다. 클라이언트가 서버에 요청을 하면 서버는 응답을 주고 통신을 끊지 않고 계속해서 데이터를 보낸다. 클라이언트도 계속해서 데이터를 받는다.
위의 예제를 만들어 볼 건데, 사실 스프링 웹플럭스가 나오기 전에도 구현은 가능했지만 꽤 어려웠다.
하지만 지금은 스프링 웹플럭스 덕분에 쉽게 구현이 가능하다.
1. Pom.xml
spring-boot-starter-webflux
메이븐 pom.xml에 스프링부트 스타터 웹플럭스를 추가한다. 나머지는 이클립스든 인텔리J든 기본적인 스프링웹 MVC 설정과 동일하다.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.12</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2. 자바 클래스를 만든다.
컨트롤러 하나, 서비스하나, Vo객체 하나
역순으로 Dish.java, KitchenService.java, ServerController.java
2-1. Dish.java
package com.example.demo;
public class Dish {
private String description;
private boolean delivered = false;
public static Dish deliver(Dish dish){
Dish deliveredDish = new Dish(dish.description);
deliveredDish.delivered = true;
return deliveredDish;
}
public Dish(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public boolean isDelivered(){
return delivered;
}
@Override
public String toString() {
return "Dish{" +
"description='" + description + '\'' +
", delivered=" + delivered +
'}';
}
}
2-2. KitchenService.java
package com.example.demo;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
@Service
public class KitchenService {
/**
* 요리 스트림 생성
*/
Flux<Dish> getDishes(){
return Flux.<Dish> generate(sink -> sink.next(randomDish()))
.delayElements(Duration.ofMillis(250));
}
/**
* 요리 무작위 선택
*/
private Dish randomDish(){
return menu.get(picker.nextInt(menu.size()));
}
private List<Dish> menu = Arrays.asList(
new Dish("Sesame chicken"),
new Dish("Lo mein noodles, plain"),
new Dish("Sweet & sour beef"));
private Random picker = new Random();
}
2-3. ServerController.java
package com.example.demo;
import reactor.core.publisher.Flux;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ServerController {
private final KitchenService kitchen;
public ServerController(KitchenService kitchen) {
this.kitchen = kitchen;
}
@GetMapping(value = "/server", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
Flux<Dish> serveDishes(){
return this.kitchen.getDishes();
}
}
http://localhost:8080/server를 호출한다.
딱히 어려운 로직은 없다.
다만 컨트롤러에 MediaType.TEXT_EVENT_STREAM_VALUE 라는 옵션을 넣었다.
예제를 따라 해서 실행이 된다면, 스트리밍 방식으로 동작하는 웹 서비스를 만드는 데 성공했다.
간단한 소스라 딱히 설명은 필요 없지만 컨트롤러를 호출하면 랜덤으로 Dish 정보를 생성해서 클라이언트에게 0.25초 단위로 정보를 넘겨준다. 윈도우의 작업지시창을 열어보면 메모리가 문제가 발생하지도 않는다.
Json 스트리밍을 만들어 반환하는 웹 컨트롤러를 만들었다.
리액티브 웹에 대한 최소한의 이해를 하기 위해 적절한 예제라 본다. 뭔가 더 알고 싶은 욕구가 뿜뿜....?
'슬기로운 자바 개발자 생활 > 스프링 및 자바웹 서비스' 카테고리의 다른 글
스프링 프레임워크 기본 다지기 (0) | 2023.06.24 |
---|---|
Java map object value to integer Casting (0) | 2023.06.15 |
Mybatis에서 Oracle Procedure 사용 (0) | 2023.06.04 |
스프링에서 oracle declare 문 사용해보기 (0) | 2023.05.14 |
RestTemplate와 WebClient 사용해보기 (0) | 2023.03.14 |
댓글