๐ Spring WebFlux: ๋ฆฌ์กํฐ๋ธ ์น ์ ํ๋ฆฌ์ผ์ด์ ๊ฐ๋ฐ์ ์ ์ธ๊ณ! ๐

์๋ ํ์ธ์, ๊ฐ๋ฐ์ ์ฌ๋ฌ๋ถ! ์ค๋์ ์ ๋ง ํซํ ์ฃผ์ ๋ก ์ฐพ์์์ด์. ๋ฐ๋ก 'Spring WebFlux'์ ๋ํด ๊น์ด ํํค์ณ๋ณผ ๊ฑฐ์์. ์ด ๊ธ์ ์ฝ๊ณ ๋๋ฉด ์ฌ๋ฌ๋ถ๋ ๋ฆฌ์กํฐ๋ธ ํ๋ก๊ทธ๋๋ฐ์ ๊ณ ์๊ฐ ๋ ์ ์์ ๊ฑฐ์์! ๐
์ฐ์ , ์ฌ๋ฅ๋ท(https://www.jaenung.net)์์ Java ๊ฐ๋ฐ ๊ด๋ จ ์ฌ๋ฅ์ ์ฐพ๊ณ ๊ณ์ ๋ค๋ฉด, ์ด ๊ธ์ด ์ฌ๋ฌ๋ถ์๊ฒ ํฐ ๋์์ด ๋ ๊ฑฐ์์. Spring WebFlux๋ Java ๊ฐ๋ฐ์๋ค ์ฌ์ด์์ ์ดํซํ ๊ธฐ์ ์ด๋๊น์! ์, ๊ทธ๋ผ ์์ํด๋ณผ๊น์? ๐โโ๏ธ๐จ
์ ๊น! ๐ค Spring WebFlux๊ฐ ๋ญ์ง ์ ๋ชจ๋ฅด๊ฒ ๋ค๊ณ ์? ๊ฑฑ์ ๋ง์ธ์! ์ด ๊ธ์์ ์์ฃผ ์ฝ๊ณ ์ฌ๋ฏธ์๊ฒ ์ค๋ช ํด๋๋ฆด๊ฒ์. ๋ง์น ์น๊ตฌ์ ์นดํกํ๋ฏ์ด ์ค๋ช ํ ํ ๋ ํธํ๊ฒ ์ฝ์ด์ฃผ์ธ์~
1. Spring WebFlux๋ ๋ญ์ผ? ๐คทโโ๏ธ
Spring WebFlux๋ Spring Framework 5์์ ์๋กญ๊ฒ ์ถ๊ฐ๋ ๋ชจ๋์ด์์. ๊ฐ๋จํ ๋งํ๋ฉด, ๋น๋๊ธฐ-๋ ผ๋ธ๋กํน ๋ฆฌ์กํฐ๋ธ ๊ฐ๋ฐ์ ํ ์ ์๊ฒ ํด์ฃผ๋ ์น ํ๋ ์์ํฌ๋๋๋ค. ์ด, ๋ญ๊ฐ ์ด๋ ค์ด ๋จ์ด๋ค์ด ๋์์ฃ ? ใ ใ ใ ๊ฑฑ์ ๋ง์ธ์. ํ๋์ฉ ํ์ด์ ์ค๋ช ํด๋๋ฆด๊ฒ์!
- ๋น๋๊ธฐ(Asynchronous): ์์ ์ ์์ฒญํ๊ณ ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋ค๋ฆฌ์ง ์๊ณ ๋ค๋ฅธ ์ผ์ ํ ์ ์์ด์. ๋ง์น ๋ผ๋ฉด ๋์ด๋ฉด์ ํด๋ํฐ ํ๋ ๊ฒ์ฒ๋ผ์! ๐จโ๐ณ๐ฑ
- ๋ ผ๋ธ๋กํน(Non-blocking): ์์ ์ด ๋๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฌ์ง ์๊ณ ๊ณ์ ๋ค๋ฅธ ์ผ์ ํ ์ ์์ด์. ๋ผ๋ฉด ๋ฌผ ๋์ด๋ ๋์ ๋ฐฉ ์ฒญ์ํ๋ ๊ฑฐ๋ ๋น์ทํด์! ๐งน๐
- ๋ฆฌ์กํฐ๋ธ(Reactive): ๋ณํ์ ๋ฐ์ํ๋ ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์์ด์์. ์นดํก ์๋ฆผ์์ ๋ฐ๋ก ๋ฐ์ํ๋ ๊ฒ์ฒ๋ผ์! ๐ณ๐
Spring WebFlux๋ฅผ ์ฌ์ฉํ๋ฉด ์ด๋ฐ ๋ฉ์ง ๊ธฐ๋ฅ๋ค์ ํ์ฉํด์ ์ด๊ณ ์ ์น ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ง๋ค ์ ์์ด์. ์, ๋ฒ์จ๋ถํฐ ๊ธฐ๋๋์ง ์๋์? ๐
2. Spring WebFlux vs Spring MVC: ๋ญ๊ฐ ๋ค๋ฅธ ๊ฑฐ์ผ? ๐ค
์๋ง ์ฌ๋ฌ๋ถ ์ค์๋ Spring MVC๋ฅผ ์ด๋ฏธ ์ฌ์ฉํด๋ณธ ๋ถ๋ค์ด ๋ง์ ๊ฑฐ์์. ๊ทธ๋ผ Spring WebFlux๋ ๋ญ๊ฐ ๋ค๋ฅธ์ง ๊ถ๊ธํ์์ฃ ? ์, ๋น๊ตํด๋ณผ๊น์?
Spring MVC
- ๋๊ธฐ-๋ธ๋กํน ๋ชจ๋ธ
- ํ ์์ฒญ๋น ํ๋์ ์ค๋ ๋
- ์ ํต์ ์ธ ์๋ธ๋ฆฟ ๊ธฐ๋ฐ
- ์ฝ๊ณ ์ต์ํด์
Spring WebFlux
- ๋น๋๊ธฐ-๋ ผ๋ธ๋กํน ๋ชจ๋ธ
- ์ ์ ์์ ์ค๋ ๋๋ก ๋ง์ ์์ฒญ ์ฒ๋ฆฌ
- Netty ๊ฐ์ ๋น๋๊ธฐ ์๋ฒ ์ฌ์ฉ
- ๋์ ๋์์ฑ๊ณผ ํ์ฅ์ฑ
Spring MVC๊ฐ ํธ์ํ ์ฌ๋ฆฌํผ ๊ฐ๋ค๋ฉด, Spring WebFlux๋ ์ด๊ฒฝ๋ ๋ฌ๋ํ ๊ฐ์ ๊ฑฐ์์. ๋น ๋ฅด๊ณ ๊ฐ๋ณ์ง๋ง, ์ต์ํด์ง๋ ๋ฐ ์๊ฐ์ด ์ข ๊ฑธ๋ฆด ์ ์์ฃ . ํ์ง๋ง ์ต์ํด์ง๋ฉด? ์, ์ธ์์ ๋ค ๋ฐ์ด๋ค๋ ์ ์์ ๊ฑฐ์์! ๐โโ๏ธ๐จ
3. Spring WebFlux์ ํต์ฌ ๊ฐ๋ ๋ค ๐ง
์, ์ด์ Spring WebFlux์ ํต์ฌ ๊ฐ๋ ๋ค์ ์์๋ณผ ์ฐจ๋ก์์. ์ด๋ ค์ ๋ณด์ผ ์ ์์ง๋ง, ์ฐจ๊ทผ์ฐจ๊ทผ ์ค๋ช ํด๋๋ฆด๊ฒ์. ์ค๋น๋์ จ๋์? ๊ณ ๊ณ ! ๐
3.1. Reactive Streams ๐
Reactive Streams๋ ๋น๋๊ธฐ ๋ฐ์ดํฐ ์ฒ๋ฆฌ์ ํ์ค์ด์์. ๋ง์น ๋ฌผ์ด ํ๋ฅด๋ ๊ฒ์ฒ๋ผ ๋ฐ์ดํฐ๊ฐ ํ๋ฌ๊ฐ๋ ๊ฑธ ์์ํด๋ณด์ธ์. ์ด ํ๋ฆ์ ์ ์ดํ๊ณ ๊ด๋ฆฌํ๋ ๊ฒ Reactive Streams์ ์ญํ ์ด์์.
๐ก Tip: Reactive Streams์ ํต์ฌ ์ธํฐํ์ด์ค๋ Publisher, Subscriber, Subscription, Processor์์. ์ด๋ค์ด ํ๋ ฅํด์ ๋ฐ์ดํฐ์ ํ๋ฆ์ ๊ด๋ฆฌํ๋ต๋๋ค!
3.2. Reactor ๐ฌ
Reactor๋ Spring WebFlux์ ๊ธฐ๋ฐ์ด ๋๋ ๋ฆฌ์กํฐ๋ธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์. Reactive Streams๋ฅผ ๊ตฌํํ๊ณ ์์ฃ . Reactor์ ํต์ฌ ํ์ ์ Mono์ Flux์์.
- Mono: 0 ๋๋ 1๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ๋ค๋ฃจ๋ Publisher
- Flux: 0-N๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ๋ค๋ฃจ๋ Publisher
Mono์ Flux๋ฅผ ์ฌ์ฉํ๋ฉด ๋น๋๊ธฐ ๋ฐ์ดํฐ ์คํธ๋ฆผ์ ์์ฃผ ์ฐ์ํ๊ฒ ๋ค๋ฃฐ ์ ์์ด์. ๋ง์น ๋ง๋ฒ์ฌ๊ฐ ๋ ๊ฒ ๊ฐ์ ๊ธฐ๋ถ์ด ๋ค ๊ฑฐ์์! ๐งโโ๏ธโจ
3.3. ํจ์ํ ์๋ํฌ์ธํธ ๐ฏ
Spring WebFlux์์๋ ์ ํต์ ์ธ ์ ๋ ธํ ์ด์ ๊ธฐ๋ฐ ์ปจํธ๋กค๋ฌ ๋์ ํจ์ํ ์๋ํฌ์ธํธ๋ฅผ ์ฌ์ฉํ ์ ์์ด์. ์ด๊ฒ ๋ญ๋๊ณ ์? ๊ฐ๋จํ ๋งํด์, HTTP ์์ฒญ์ ํจ์๋ก ์ฒ๋ฆฌํ๋ ๋ฐฉ์์ด์์.
@Bean
public RouterFunction<serverresponse> route(Handler handler) {
return RouterFunctions
.route(GET("/hello").and(accept(MediaType.TEXT_PLAIN)), handler::hello)
.andRoute(POST("/echo").and(accept(MediaType.TEXT_PLAIN)), handler::echo);
}
</serverresponse>
์ด๋ฐ ์์ผ๋ก ๋ผ์ฐํ ์ ์ ์ํ ์ ์์ด์. ๊น๋ํ๊ณ ๋ช ํํ์ฃ ? ๐
4. Spring WebFlux ์์ํ๊ธฐ ๐
์, ์ด์ ์ค์ ๋ก Spring WebFlux๋ฅผ ์ฌ์ฉํด๋ณผ ์ฐจ๋ก์์! ์ด๋ป๊ฒ ์์ํ๋ฉด ๋ ๊น์? ์ฐจ๊ทผ์ฐจ๊ทผ ๋ฐ๋ผ์๋ณด์ธ์~
4.1. ํ๋ก์ ํธ ์ค์ โ๏ธ
๋จผ์ , Spring Initializr(https://start.spring.io/)์์ ์ ํ๋ก์ ํธ๋ฅผ ๋ง๋ค์ด๋ณผ๊น์?
- Project: Gradle Project
- Language: Java
- Spring Boot: ์ต์ ์์ ๋ฒ์ ์ ํ
- Group: com.example
- Artifact: webflux-demo
- Dependencies: Spring Reactive Web
์ด๋ ๊ฒ ์ค์ ํ๊ณ 'Generate' ๋ฒํผ์ ๋๋ฅด๋ฉด ํ๋ก์ ํธ๊ฐ ๋ค์ด๋ก๋๋ผ์. ์ฝ์ฃ ? ๐
4.2. ์ฒซ ๋ฒ์งธ Handler ๋ง๋ค๊ธฐ ๐ ๏ธ
์, ์ด์ ์ฒซ ๋ฒ์งธ Handler๋ฅผ ๋ง๋ค์ด๋ณผ๊น์? Handler๋ ์ค์ ๋ก ์์ฒญ์ ์ฒ๋ฆฌํ๋ ๋ก์ง์ ๋ด๋นํด์.
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
@Component
public class GreetingHandler {
public Mono<serverresponse> hello(ServerRequest request) {
return ServerResponse.ok().bodyValue("Hello, WebFlux!");
}
}
</serverresponse>
์! ์ฐ๋ฆฌ์ ์ฒซ ๋ฒ์งธ Handler๊ฐ ์์ฑ๋์ด์. ์ด๋์, ์๊ฐ๋ณด๋ค ์ด๋ ต์ง ์์ฃ ? ๐
4.3. Router ์ค์ ํ๊ธฐ ๐บ๏ธ
์ด์ Router๋ฅผ ์ค์ ํด๋ณผ ์ฐจ๋ก์์. Router๋ ๋ค์ด์ค๋ HTTP ์์ฒญ์ ์ ์ ํ Handler๋ก ์ฐ๊ฒฐํด์ฃผ๋ ์ญํ ์ ํด์.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;
import static org.springframework.web.reactive.function.server.RequestPredicates.*;
@Configuration
public class GreetingRouter {
@Bean
public RouterFunction<serverresponse> route(GreetingHandler greetingHandler) {
return RouterFunctions
.route(GET("/hello").and(accept(MediaType.TEXT_PLAIN)), greetingHandler::hello);
}
}
</serverresponse>
์ง์~ ์ด์ "/hello" ๊ฒฝ๋ก๋ก GET ์์ฒญ์ด ์ค๋ฉด ์ฐ๋ฆฌ์ GreetingHandler๊ฐ ์ฒ๋ฆฌํ ๊ฑฐ์์. ๋ฉ์ง์ฃ ? ๐
4.4. ์ ํ๋ฆฌ์ผ์ด์ ์คํํ๊ธฐ ๐โโ๏ธ
๋ชจ๋ ์ค๋น๊ฐ ๋๋ฌ์ด์! ์ด์ ์ ํ๋ฆฌ์ผ์ด์ ์ ์คํํด๋ณผ๊น์?
@SpringBootApplication
public class WebfluxDemoApplication {
public static void main(String[] args) {
SpringApplication.run(WebfluxDemoApplication.class, args);
}
}
์ด ํด๋์ค๋ฅผ ์คํํ๋ฉด ์ ํ๋ฆฌ์ผ์ด์ ์ด ์์๋ผ์. ๊ทธ๋ฆฌ๊ณ ๋ธ๋ผ์ฐ์ ์์ http://localhost:8080/hello ๋ฅผ ์ด์ด๋ณด์ธ์. "Hello, WebFlux!"๋ผ๋ ๋ฉ์์ง๊ฐ ๋ณด์ผ ๊ฑฐ์์. ์ถํํฉ๋๋ค! ์ฌ๋ฌ๋ถ์ ์ฒซ WebFlux ์ ํ๋ฆฌ์ผ์ด์ ์ด ๋์ํ๊ณ ์์ด์! ๐๐
5. Spring WebFlux์ ๊ฐ๋ ฅํ ๊ธฐ๋ฅ๋ค ๐ช
์, ์ด์ ๊ธฐ๋ณธ์ ์ธ ๊ฒ๋ค์ ์์์ผ๋ Spring WebFlux์ ๋ ๊ฐ๋ ฅํ ๊ธฐ๋ฅ๋ค์ ์ดํด๋ณผ๊น์? ์ค๋น๋์ จ๋์? ๊ณ ๊ณ ! ๐
5.1. ๋น๋๊ธฐ ๋ ผ๋ธ๋กํน ์ฒ๋ฆฌ โก
Spring WebFlux์ ๊ฐ์ฅ ํฐ ์ฅ์ ์ค ํ๋๋ ๋น๋๊ธฐ ๋ ผ๋ธ๋กํน ์ฒ๋ฆฌ์์. ์ด๊ฒ ๋ญ๊ฐ ์ข๋๊ณ ์? ์์ฒญ๋ ์์ ๋์ ์์ฒญ์ ์ฒ๋ฆฌํ ์ ์๋ค๋ ๊ฑฐ์ฃ !
๐ก ์์ํด๋ณด์ธ์: ์ฌ๋ฌ๋ถ์ด ์นดํ ์ฃผ์ธ์ด๋ผ๊ณ ํด๋ณผ๊น์? Spring MVC๋ ์๋ ํ ๋ช ๋น ์ง์ ํ ๋ช ์ ๋ฐฐ์ ํ๋ ๊ฑฐ์์. ๊ทผ๋ฐ Spring WebFlux๋? ์์์ ์ง์์ด ์์ฒญ๋ ์์ ์๋์ ๋์์ ์๋ํ ์ ์๋ ๊ฑฐ์ฃ ! ์, ์์๋ง ํด๋ ํจ์จ์ ์ด์ฃ ? ๐ฒ
์, ๊ทธ๋ผ ์ค์ ์ฝ๋๋ก ์ด๋ป๊ฒ ๊ตฌํํ๋์ง ๋ณผ๊น์?
@GetMapping("/users")
public Flux<user> getAllUsers() {
return userRepository.findAll();
}
</user>
์ด ์ฝ๋๋ ๋ชจ๋ ์ฌ์ฉ์๋ฅผ ๊ฐ์ ธ์ค๋ ์๋ํฌ์ธํธ์์. Flux๋ฅผ ๋ฐํํจ์ผ๋ก์จ, ๋ฐ์ดํฐ๋ฅผ ์คํธ๋ฆฌ๋ฐ ๋ฐฉ์์ผ๋ก ํด๋ผ์ด์ธํธ์๊ฒ ์ ์กํ ์ ์์ด์. ์ฆ, ์ ์ฒด ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋ค๋ฆฌ์ง ์๊ณ ๋ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ ์ ์๋ ๊ฑฐ์ฃ . ์ฉ๋ค... ๐
5.2. ๋ฐฑํ๋ ์ (Backpressure) ์ ์ด ๐๏ธ
๋ฐฑํ๋ ์ ๋ผ๋ ๋ง, ๋ค์ด๋ณด์ จ๋์? ์๋ง ์ฒ์ ๋ค์ด๋ณด์ ๋ถ๋ค์ด ๋ง์ ๊ฑฐ์์. ์ฝ๊ฒ ์ค๋ช ํด๋๋ฆด๊ฒ์!
๋ฐฑํ๋ ์ ๋ ๋ฐ์ดํฐ๋ฅผ ์๋นํ๋ ์๋๋ฅผ ์ ์ดํ๋ ๋ฉ์ปค๋์ฆ์ด์์. ์ฆ, ๋ฐ๋ ์ชฝ์์ "์ผ, ์ฒ์ฒํ ๋ณด๋ด์ค!"๋ผ๊ณ ๋งํ ์ ์๋ ๊ฑฐ์ฃ . ๐
@GetMapping(value = "/numbers", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<integer> getNumbers() {
return Flux.range(1, 100)
.delayElements(Duration.ofMillis(100))
.log();
}
</integer>
์ด ์ฝ๋๋ 1๋ถํฐ 100๊น์ง์ ์ซ์๋ฅผ 0.1์ด ๊ฐ๊ฒฉ์ผ๋ก ์คํธ๋ฆฌ๋ฐํด์. ํด๋ผ์ด์ธํธ๊ฐ ์ฒ๋ฆฌํ ์ ์๋ ์๋์ ๋ง์ถฐ ๋ฐ์ดํฐ๋ฅผ ์ ์กํ๋ ๊ฑฐ์ฃ . coolํ์ง ์๋์? ๐
5.3. ํจ์ํ ํ๋ก๊ทธ๋๋ฐ ์คํ์ผ ๐งฎ
Spring WebFlux๋ ํจ์ํ ํ๋ก๊ทธ๋๋ฐ ์คํ์ผ์ ์ง์ํด์. ์ด๊ฒ ๋ญ๊ฐ ์ข๋๊ณ ์? ์ฝ๋๊ฐ ๋ ๊ฐ๊ฒฐํ๊ณ ํ ์คํธํ๊ธฐ ์ฌ์์ง๋ต๋๋ค!
@Bean
public RouterFunction<serverresponse> route(Handler handler) {
return RouterFunctions
.route(GET("/hello").and(accept(MediaType.TEXT_PLAIN)), handler::hello)
.andRoute(POST("/echo").and(accept(MediaType.TEXT_PLAIN)), handler::echo);
}
</serverresponse>
์ด๋ฐ ์์ผ๋ก ๋ผ์ฐํ ์ ์ ์ํ ์ ์์ด์. ํจ์ํ ์คํ์ผ๋ก ์์ฑํ๋ฉด ์ฝ๋์ ์๋๊ฐ ๋ ๋ช ํํด์ง๊ณ , ์ฌ์ฌ์ฉ์ฑ๋ ๋์์ ธ์. ์์ ๊ฐ์ด๋! ๐
5.4. ๋ฆฌ์กํฐ๋ธ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ง์ ๐พ
Spring WebFlux๋ ๋ฆฌ์กํฐ๋ธ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ ์ง์ํด์. ๋ชฝ๊ณ DB, Cassandra, Redis ๋ฑ์ NoSQL ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ด์ธ๋ฆฌ์ฃ .
@Repository
public interface UserRepository extends ReactiveCrudRepository<user string> {
Flux<user> findByLastName(String lastName);
}
</user></user>
์ด๋ ๊ฒ ๋ฆฌ์กํฐ๋ธ ๋ ํฌ์งํ ๋ฆฌ๋ฅผ ์ ์ํ๋ฉด, ๋ฐ์ดํฐ๋ฒ ์ด์ค ์์ ๋ ๋น๋๊ธฐ์ ์ผ๋ก ์ฒ๋ฆฌํ ์ ์์ด์. ์์ ์ฐฐ๋ก๊ถํฉ์ด์ฃ ? ๐ซ
6. Spring WebFlux์ ์ค์ ์ฌ์ฉ ์ฌ๋ก ๐
์, ์ด์ Spring WebFlux๋ฅผ ์ด๋ค ์ํฉ์์ ์ฌ์ฉํ๋ฉด ์ข์์ง ์์๋ณผ๊น์? ์ค์ ์ฌ๋ก๋ฅผ ํตํด ์ดํด๋ณด๋ฉด ๋ ์ดํด๊ฐ ์ ๋ ๊ฑฐ์์!
6.1. ์ค์๊ฐ ๋ฐ์ดํฐ ์คํธ๋ฆฌ๋ฐ ๐
์ค์๊ฐ์ผ๋ก ๋ณํ๋ ๋ฐ์ดํฐ๋ฅผ ๋ค๋ฃจ๋ ์ ํ๋ฆฌ์ผ์ด์ ์ Spring WebFlux๊ฐ ๋ฑ์ด์์. ์๋ฅผ ๋ค์ด, ์ฃผ์ ์์ธ ์ ๋ณด๋ฅผ ์ค์๊ฐ์ผ๋ก ์ ๊ณตํ๋ ์๋น์ค๋ฅผ ๋ง๋ ๋ค๊ณ ์๊ฐํด๋ณผ๊น์?
@GetMapping(value = "/stocks", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<stockprice> getStockPrices() {
return Flux.interval(Duration.ofSeconds(1))
.map(i -> new StockPrice("APPL", 150 + new Random().nextInt(10)));
}
</stockprice>
์ด ์ฝ๋๋ 1์ด๋ง๋ค ์๋ก์ด ์ฃผ์ ๊ฐ๊ฒฉ์ ์์ฑํด์ ํด๋ผ์ด์ธํธ์๊ฒ ์ ์กํด์. ํด๋ผ์ด์ธํธ๋ ์๋ฒ์์ ์ฐ๊ฒฐ์ ์ ์งํ ์ฑ๋ก ์ค์๊ฐ์ผ๋ก ์ ๋ฐ์ดํธ๋๋ ์ ๋ณด๋ฅผ ๋ฐ์ ์ ์์ฃ . ์์ ์ค์๊ฐ์ด์ผ! ๐ฐ๏ธ
6.2. ๋์ฉ๋ ๋ฐ์ดํฐ ์ฒ๋ฆฌ ๐
์์ฒญ๋๊ฒ ํฐ ๋ฐ์ดํฐ๋ฅผ ์ฒ๋ฆฌํด์ผ ํ ๋๋ Spring WebFlux๊ฐ ๋น์ ๋ฐํด์. ์๋ฅผ ๋ค์ด, ์๋ฐฑ๋ง ๊ฐ์ ๋ก๊ทธ ํ์ผ์ ๋ถ์ํด์ผ ํ๋ค๊ณ ํด๋ณผ๊น์?
@GetMapping("/logs")
public Flux<logentry> analyzeLogs() {
return Flux.fromIterable(logFiles)
.flatMap(this::readLogFile)
.filter(log -> log.getLevel().equals("ERROR"))
.take(100);
}
</logentry>
์ด ์ฝ๋๋ ๋ก๊ทธ ํ์ผ๋ค์ ๋น๋๊ธฐ์ ์ผ๋ก ์ฝ์ด๋ค์ด๊ณ , ์๋ฌ ๋ก๊ทธ๋ง ํํฐ๋งํ ๋ค ์ฒ์ 100๊ฐ๋ง ๋ฐํํด์. ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ ์ต์ํํ๋ฉด์ ๋์ฉ๋ ๋ฐ์ดํฐ๋ฅผ ํจ์จ์ ์ผ๋ก ์ฒ๋ฆฌํ ์ ์๋ต๋๋ค. ์์ ๋๋ํด! ๐ง
6.3. ๋ง์ดํฌ๋ก์๋น์ค ์ํคํ ์ฒ ๐๏ธ
Spring WebFlux๋ ๋ง์ดํฌ๋ก์๋น์ค ์ํคํ ์ฒ์ ์ฐฐ๋ก๊ถํฉ์ด์์. ์ฌ๋ฌ ์๋น์ค ๊ฐ์ ๋น๋๊ธฐ ํต์ ์ ํจ์จ์ ์ผ๋ก ์ฒ๋ฆฌํ ์ ์๊ฑฐ๋ ์.
@GetMapping("/user/{id}/details")
public Mono<userdetails> getUserDetails(@PathVariable String id) {
return userService.getUser(id)
.flatMap(user -> Mono.zip(
accountService.getAccount(user.getAccountId()),
orderService.getOrders(user.getId())
))
.map(tuple -> new UserDetails(tuple.getT1(), tuple.getT2()));
}
</userdetails>
์ด ์ฝ๋๋ ์ฌ์ฉ์ ์ ๋ณด, ๊ณ์ข ์ ๋ณด, ์ฃผ๋ฌธ ์ ๋ณด๋ฅผ ๊ฐ๊ฐ ๋ค๋ฅธ ์๋น์ค์์ ๋น๋๊ธฐ์ ์ผ๋ก ๊ฐ์ ธ์ ์กฐํฉํด์. ์ฌ๋ฌ ์๋น์ค์ ์๋ต์ ๊ธฐ๋ค๋ฆฌ๋๋ผ ๋ธ๋กํน๋์ง ์๊ณ , ๋ชจ๋ ์ ๋ณด๊ฐ ์ค๋น๋๋ฉด ํ ๋ฒ์ ์๋ตํ ์ ์์ฃ . ์์ ํจ์จ์ ์ด์ผ! ๐
6.4. IoT (์ฌ๋ฌผ์ธํฐ๋ท) ์ ํ๋ฆฌ์ผ์ด์ ๐
IoT ๊ธฐ๊ธฐ๋ค์ ๋์์์ด ๋ฐ์ดํฐ๋ฅผ ์์ฑํ๊ณ ์ ์กํ์ฃ . ์ด๋ฐ ์ํฉ์์ Spring WebFlux์ ๋น๋๊ธฐ ์ฒ๋ฆฌ ๋ฅ๋ ฅ์ด ๋น์ ๋ฐํด์.
@MessageMapping("/sensors/{id}")
@SendTo("/topic/temperatures")
public Flux<temperature> handleSensorData(@DestinationVariable String id, Flux<temperature> temperatures) {
return temperatures
.filter(temp -> temp.getValue() > 30)
.map(temp -> {
log.info("High temperature detected from sensor {}: {}", id, temp.getValue());
return temp;
});
}
</temperature></temperature>
์ด ์ฝ๋๋ ์ผ์์์ ๋ณด๋ด๋ ์จ๋ ๋ฐ์ดํฐ ์คํธ๋ฆผ์ ์ฒ๋ฆฌํด์. 30๋๊ฐ ๋๋ ์จ๋๋ง ํํฐ๋งํด์ ๋ก๊ทธ๋ฅผ ๋จ๊ธฐ๊ณ , ํด๋ผ์ด์ธํธ์๊ฒ ์ ์กํ์ฃ . ์ค์๊ฐ์ผ๋ก ์ด์ ์งํ๋ฅผ ๊ฐ์งํ ์ ์์ด์. ์์ ์ค๋งํธํ์์! ๐ง ๐ก
7. Spring WebFlux์ ์ฑ๋ฅ๊ณผ ์ต์ ํ ๐
์, ์ด์ Spring WebFlux์ ์ฑ๋ฅ์ ๋ํด ์์ธํ ์์๋ณผ๊น์? ์ด๋ป๊ฒ ํ๋ฉด ์ต๊ณ ์ ์ฑ๋ฅ์ ๋ฝ์๋ผ ์ ์์์ง ํจ๊ป ์ดํด๋ด์!
7.1. ์ฑ๋ฅ ๋น๊ต: Spring WebFlux vs Spring MVC ๐๏ธ
๋ง์ ๊ฐ๋ฐ์๋ค์ด ๊ถ๊ธํดํ๋ ๋ถ๋ถ์ด์ฃ . "Spring WebFlux๊ฐ ์ ๋ง๋ก Spring MVC๋ณด๋ค ๋น ๋ฅผ๊น?" ๋ผ๊ณ ์. ์... ์ ๋ต์ "์ํฉ์ ๋ฐ๋ผ ๋ค๋ฅด๋ค" ์์. ใ ใ ใ
๐๏ธโโ๏ธ ๋ฒค์น๋งํฌ ๊ฒฐ๊ณผ: ์ผ๋ฐ์ ์ผ๋ก Spring WebFlux๋ ๋์์ ๋ง์ ์์ฒญ์ ์ฒ๋ฆฌํ ๋ ๋ ์ข์ ์ฑ๋ฅ์ ๋ณด์ฌ์. ํนํ I/O ์์ ์ด ๋ง์ ๊ฒฝ์ฐ์ ๊ทธ ์ฐจ์ด๊ฐ ๋๋๋ฌ์ ธ์. ํ์ง๋ง ๋จ์ํ CRUD ์์ ์์๋ Spring MVC๊ฐ ๋ ๋น ๋ฅผ ์ ์์ด์.
์ค์ํ ๊ฑด "๋ฌด์กฐ๊ฑด WebFlux๊ฐ ์ข๋ค"๊ฐ ์๋๋ผ, ์ฌ๋ฌ๋ถ์ ์ ํ๋ฆฌ์ผ์ด์ ํน์ฑ์ ๋ง๋ ๊ฑธ ์ ํํ๋ ๊ฑฐ์์. ๋ง์น ์ด๋ํ ๊ณ ๋ฅด๋ฏ์ด ๋ง์ด์ฃ ! ๐
7.2. ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ ์ต์ ํ ๐พ
Spring WebFlux์ ์ฅ์ ์ค ํ๋๋ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ ํจ์จ์ ์ผ๋ก ๊ด๋ฆฌํ ์ ์๋ค๋ ๊ฑฐ์์. ์ด๋ป๊ฒ ํ๋ฉด ๋ ๊น์?
@GetMapping("/large-data")
public Flux<data> getLargeData() {
return dataRepository.findAll()
.buffer(1000)
.delayElements(Duration.ofMillis(100));
}
</data>
์ด ์ฝ๋๋ ๋๋์ ๋ฐ์ดํฐ๋ฅผ ์ฒ๋ฆฌํ ๋ ๋ฒํผ๋ฅผ ์ฌ์ฉํด ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ ์ ์ดํ๊ณ , ๋ฐ์ดํฐ ์ ์ก ์๋๋ฅผ ์กฐ์ ํด์. ๋ง์น ๋ฌผ์ ์ปต์ ์กฐ๊ธ์ฉ ๋ฐ๋ฅด๋ ๊ฒ์ฒ๋ผ์! ๐ฐ
7.3. ๋ฐฑํ๋ ์ ํ์ฉํ๊ธฐ ๐๏ธ
์์ ๋ฐฑํ๋ ์ ์ ๋ํด ๊ฐ๋จํ ์ค๋ช ํ์์ฃ ? ์ด๊ฑธ ์ ํ์ฉํ๋ฉด ์์คํ ์ ์์ ์ฑ์ ํฌ๊ฒ ๋์ผ ์ ์์ด์.
@GetMapping("/data")
public Flux<data> getData() {
return dataRepository.findAll()
.onBackpressureBuffer(10000)
.onBackpressureDrop(data -> log.warn("Dropped: {}", data));
}
</data>
์ด ์ฝ๋๋ ํด๋ผ์ด์ธํธ๊ฐ ์ฒ๋ฆฌํ ์ ์๋ ์๋๋ณด๋ค ๋ ๋น ๋ฅด๊ฒ ๋ฐ์ดํฐ๊ฐ ์์ฑ๋ ๊ฒฝ์ฐ, ๋ฒํผ์ ์์ ์ ์ฅํ๊ฑฐ๋ ์ด๊ณผ ๋ฐ์ดํฐ๋ฅผ ๋ฒ๋ฆฌ๋ ๋ฐฉ์์ผ๋ก ๋์ํด์. ์์ ๋๋ํ์ฃ ? ์์คํ ์ด ๊ณผ๋ถํ๋๋ ๊ฑธ ๋ง์์ฃผ๋ ๊ฑฐ์์! ๐จโ๐ง
7.4. ๋น๋๊ธฐ ๋ ผ๋ธ๋กํน I/O ์ต๋ํ ํ์ฉํ๊ธฐ โก
Spring WebFlux์ ์ง๊ฐ๋ I/O ์์ ์ด ๋ง์ ์ํฉ์์ ๋ฐํ๋ผ์. ๋ฐ์ดํฐ๋ฒ ์ด์ค ์กฐํ, ์ธ๋ถ API ํธ์ถ ๋ฑ์ ๋ชจ๋ ๋น๋๊ธฐ๋ก ์ฒ๋ฆฌํ๋ฉด ์ด๋จ๊น์?
@GetMapping("/user/{id}/full-profile")
public Mono<userprofile> getUserFullProfile(@PathVariable String id) {
return userRepository.findById(id)
.flatMap(user -> Mono.zip(
accountService.getAccountDetails(user.getAccountId()),
orderService.getRecentOrders(user.getId()),
reviewService.getUserReviews(user.getId())
))
.map(tuple -> new UserProfile(tuple.getT1(), tuple.getT2(), tuple.getT3()));
}
</userprofile>
์ด ์ฝ๋๋ ์ฌ์ฉ์ ์ ๋ณด, ๊ณ์ข ์ ๋ณด, ์ต๊ทผ ์ฃผ๋ฌธ, ๋ฆฌ๋ทฐ ์ ๋ณด๋ฅผ ๋ชจ๋ ๋น๋๊ธฐ์ ์ผ๋ก ๊ฐ์ ธ์์ ์กฐํฉํด์. ๋ง์น ์ฌ๋ฌ ๊ฐ์ง ์ฌ๋ฃ๋ฅผ ๋์์ ์ค๋นํด์ ํ ๋ฒ์ ์๋ฆฌ๋ฅผ ์์ฑํ๋ ๊ฒ์ฒ๋ผ์! ๐จโ๐ณ
8. Spring WebFlux์ ํ ์คํ ์ ๋ต ๐งช
์ฝ๋๋ฅผ ์์ฑํ๋ค๋ฉด ๋น์ฐํ ํ ์คํธ๋ ํด์ผ๊ฒ ์ฃ ? Spring WebFlux ์ ํ๋ฆฌ์ผ์ด์ ์ ์ด๋ป๊ฒ ํ ์คํธํ ์ ์๋์ง ์์๋ณผ๊น์?
8.1. WebTestClient ์ฌ์ฉํ๊ธฐ ๐ต๏ธโโ๏ธ
Spring WebFlux๋ WebTestClient๋ผ๋ ๊ฐ๋ ฅํ ํ ์คํธ ๋๊ตฌ๋ฅผ ์ ๊ณตํด์. ์ด๋ฅผ ์ฌ์ฉํ๋ฉด ์๋ํฌ์ธํธ๋ฅผ ์ฝ๊ฒ ํ ์คํธํ ์ ์์ฃ .
@SpringBootTest
@AutoConfigureWebTestClient
class UserControllerTest {
@Autowired
private WebTestClient webTestClient;
@Test
void testGetUser() {
webTestClient.get().uri("/users/{id}", "1")
.exchange()
.expectStatus().isOk()
.expectBody(User.class)
.value(user -> assertThat(user.getId()).isEqualTo("1"));
}
}
์ด ํ ์คํธ ์ฝ๋๋ /users/{id} ์๋ํฌ์ธํธ๋ฅผ ํธ์ถํ๊ณ , ์๋ต ์ํ์ body๋ฅผ ๊ฒ์ฆํด์. ๋ง์น ํ์ ์ด ์ฆ๊ฑฐ๋ฅผ ์ฐพ๋ ๊ฒ์ฒ๋ผ ๊ผผ๊ผผํ๊ฒ ํ์ธํ ์ ์์ด์! ๐ต๏ธโโ๏ธ
8.2. StepVerifier๋ก Flux์ Mono ํ ์คํธํ๊ธฐ ๐
Reactor๋ StepVerifier๋ผ๋ ์ ํธ๋ฆฌํฐ๋ฅผ ์ ๊ณตํด์. ์ด๋ฅผ ์ฌ์ฉํ๋ฉด Flux๋ Mono์ ๋์์ ๋จ๊ณ๋ณ๋ก ๊ฒ์ฆํ ์ ์์ฃ .
@Test
void testGetAllUsers() {
Flux<user> userFlux = userService.getAllUsers();
StepVerifier.create(userFlux)
.expectNextCount(3)
.expectComplete()
.verify();
}
</user>
์ด ํ ์คํธ๋ userFlux๊ฐ ์ ํํ 3๊ฐ์ User ๊ฐ์ฒด๋ฅผ ๋ฐฉ์ถํ๊ณ ์๋ฃ๋๋์ง ํ์ธํด์. ๋ง์น ํ๋ฅด๋ ๊ฐ๋ฌผ์์ ๋ฌผ๊ณ ๊ธฐ๋ฅผ ํ๋ํ๋ ์ธ๋ ๊ฒ์ฒ๋ผ ์ ํํ๊ฒ ๊ฒ์ฆํ ์ ์์ด์! ๐๐ ๐ก
8.3. ๋ชฉ(Mock) ๊ฐ์ฒด ํ์ฉํ๊ธฐ ๐ญ
์ธ๋ถ ์์กด์ฑ์ด ์๋ ๊ฒฝ์ฐ, ๋ชฉ ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํด ํ ์คํธํ ์ ์์ด์. Spring WebFlux์์๋ Mockito ๊ฐ์ ๋ชฉ ํ๋ ์์ํฌ๋ฅผ ์ฌ์ฉํ ์ ์์ฃ .
@Test
void testGetUserWithMockedRepository() {
User mockUser = new User("1", "John Doe");
when(userRepository.findById("1")).thenReturn(Mono.just(mockUser));
Mono<user> result = userService.getUser("1");
StepVerifier.create(result)
.expectNext(mockUser)
.expectComplete()
.verify();
}
</user>
์ด ํ ์คํธ๋ userRepository๋ฅผ ๋ชฉ ๊ฐ์ฒด๋ก ๋์ฒดํด์ userService์ ๋์์ ๊ฒ์ฆํด์. ๋ง์น ์ฐ๊ทน์์ ๋ฐฐ์ฐ๊ฐ ๊ฐ์ง ์ํ์ ์ฌ์ฉํ๋ ๊ฒ์ฒ๋ผ, ์ค์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์์ด๋ ํ ์คํธํ ์ ์์ด์! ๐ญ
9. Spring WebFlux์ ์ค์ ์ ์ฉ ์ฌ๋ก ๐
์, ์ด์ ์ค์ ๋ก ๊ธฐ์ ๋ค์ด Spring WebFlux๋ฅผ ์ด๋ป๊ฒ ํ์ฉํ๊ณ ์๋์ง ์ดํด๋ณผ๊น์? ๋ช ๊ฐ์ง ํฅ๋ฏธ๋ก์ด ์ฌ๋ก๋ฅผ ์๊ฐํด๋๋ฆด๊ฒ์!
9.1. Netflix์ API ๊ฒ์ดํธ์จ์ด ๐ฌ
Netflix๋ Spring WebFlux๋ฅผ ์ฌ์ฉํด API ๊ฒ์ดํธ์จ์ด๋ฅผ ๊ตฌ์ถํ์ด์. ์์ฒญ๋ ํธ๋ํฝ์ ์ฒ๋ฆฌํด์ผ ํ๋ Netflix์๊ฒ WebFlux์ ๋น๋๊ธฐ ์ฒ๋ฆฌ ๋ฅ๋ ฅ์ ์๋ฒฝํ ์ ํ์ด์์ฃ .
๐ฅ Netflix์ ์ฑ๊ณผ: Spring WebFlux ๋์ ํ, ๋์ผํ ํ๋์จ์ด๋ก ์ด๋น ์ฒ๋ฆฌํ ์ ์๋ ์์ฒญ ์๊ฐ ํฌ๊ฒ ์ฆ๊ฐํ์ด์. ๋ํ ์๋ต ์๊ฐ๋ ๋์ ๋๊ฒ ๊ฐ์ ๋์๋ต๋๋ค!
9.2. Pivotal์ ์ค์๊ฐ ๋์๋ณด๋ ๐
Pivotal์ Spring WebFlux๋ฅผ ์ฌ์ฉํด ์ค์๊ฐ ๋ชจ๋ํฐ๋ง ๋์๋ณด๋๋ฅผ ๋ง๋ค์์ด์. ์๋ง์ ์๋ฒ์์ ๋ค์ด์ค๋ ๋ฉํธ๋ฆญ ๋ฐ์ดํฐ๋ฅผ ์ค์๊ฐ์ผ๋ก ์ฒ๋ฆฌํ๊ณ ์๊ฐํํ๋ ๋ฐ WebFlux๊ฐ ํฐ ์ญํ ์ ํ์ฃ .
WebFlux์ ๋ฆฌ์กํฐ๋ธ ์คํธ๋ฆผ ์ฒ๋ฆฌ ๋ฅ๋ ฅ ๋๋ถ์, ๋์๋ณด๋๋ ์ง์ฐ ์์ด ์ค์๊ฐ์ผ๋ก ์ ๋ฐ์ดํธ๋ ์ ์์์ด์.- ์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์ฌ๋ฅ๋ท์ ์ต์ AI ๊ธฐ์ ๊ณผ ๋ฒ๋ฅ ์ ๊ธฐ๋ฐํ์ฌ ์์ฌ์ ์ง์ ์ฌ์ฐ๊ถ์ ์ ๊ทน์ ์ผ๋ก ๋ณดํธํ๋ฉฐ,
๋ฌด๋จ ์ฌ์ฉ ๋ฐ ์นจํด ํ์์ ๋ํด ๋ฒ์ ๋์์ ํ ๊ถ๋ฆฌ๋ฅผ ๋ณด์ ํฉ๋๋ค.
ยฉ 2025 ์ฌ๋ฅ๋ท | All rights reserved.
๋๊ธ 0๊ฐ