๐Ÿš€ Spring WebFlux: ๋ฆฌ์•กํ‹ฐ๋ธŒ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐœ๋ฐœ์˜ ์‹ ์„ธ๊ณ„! ๐ŸŒŸ

์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐Ÿš€ 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/)์—์„œ ์ƒˆ ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค์–ด๋ณผ๊นŒ์š”?

  1. Project: Gradle Project
  2. Language: Java
  3. Spring Boot: ์ตœ์‹  ์•ˆ์ • ๋ฒ„์ „ ์„ ํƒ
  4. Group: com.example
  5. Artifact: webflux-demo
  6. 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์˜ ๋ฆฌ์•กํ‹ฐ๋ธŒ ์ŠคํŠธ๋ฆผ ์ฒ˜๋ฆฌ ๋Šฅ๋ ฅ ๋•๋ถ„์—, ๋Œ€์‹œ๋ณด๋“œ๋Š” ์ง€์—ฐ ์—†์ด ์‹ค์‹œ๊ฐ„์œผ๋กœ ์—…๋ฐ์ดํŠธ๋  ์ˆ˜ ์žˆ์—ˆ์–ด์š”.