2025-04-08T19:56:24

This commit is contained in:
2025-04-08 19:56:24 +09:00
parent a75a1dbd0f
commit eef061c1c9
100 changed files with 18639 additions and 0 deletions

View File

@@ -0,0 +1,42 @@
아래는 요청하신 **"스프링부트 웹플럭스 시리즈"**의 **1장: 웹플럭스란 무엇인가?**에 대한 초안입니다. 자연스럽고 이해하기 쉬운 문체로 작성했으며, 초보자와 중급 개발자 모두에게 유용할 수 있도록 개념을 명확히 설명했습니다.
---
## 1. 웹플럭스란 무엇인가?
### 스프링 웹플럭스 소개
스프링 웹플럭스(Spring WebFlux)는 스프링 프레임워크 5에서 처음 도입된 반응형 웹 애플리케이션 개발을 위한 모듈입니다. 기존의 스프링 MVC가 동기적이고 블로킹 방식으로 동작하는 데 반해, 웹플럭스는 비동기와 논블로킹을 기반으로 설계되었습니다. 이는 대규모 트래픽이나 실시간 데이터 처리가 필요한 현대 애플리케이션의 요구사항을 충족하기 위해 탄생한 기술입니다.
웹플럭스는 리액티브 스트림(Reactive Streams) 사양을 구현하며, 데이터를 스트림 형태로 처리하는 새로운 패러다임을 제공합니다. 이를 통해 개발자는 높은 처리량과 낮은 지연 시간을 요구하는 시스템을 더 효율적으로 구축할 수 있습니다. 스프링부트와 결합하면 설정이 간편해지고, 스프링 생태계의 강력한 기능을 그대로 활용할 수 있다는 점도 큰 장점입니다.
### 기존 스프링 MVC와의 차이점
스프링 MVC는 오랫동안 자바 웹 개발의 표준으로 자리 잡아 왔습니다. 하지만 스프링 MVC는 기본적으로 서블릿(Servlet) 기반의 동기 처리 모델을 따릅니다. 즉, 클라이언트의 요청이 들어오면 스레드가 해당 요청을 처리하는 동안 다른 작업을 기다리게 됩니다. 이 방식은 스레드 풀의 크기에 따라 처리 가능한 요청 수가 제한되며, 데이터베이스 호출이나 외부 API 요청처럼 시간이 오래 걸리는 작업에서 병목 현상이 발생할 수 있습니다.
반면, 웹플럭스는 논블로킹 I/O와 이벤트 루프를 활용합니다. 요청이 들어오면 스레드가 블록되지 않고, 작업이 완료될 때까지 다른 요청을 처리할 수 있습니다. 이를 통해 적은 수의 스레드로도 훨씬 더 많은 요청을 동시에 다룰 수 있습니다. 아래 표는 두 프레임워크의 주요 차이점을 간단히 정리한 것입니다.
| **특징** | **스프링 MVC** | **스프링 웹플럭스** |
|---------------------|---------------------------------|--------------------------------|
| 처리 방식 | 동기 및 블로킹 | 비동기 및 논블로킹 |
| 기반 기술 | 서블릿 API | Netty, Undertow 등 리액터 기반 |
| 데이터 처리 | 단일 응답 (Mono 없음) | 스트림 (Mono, Flux) |
| 적합한 환경 | 전통적인 웹 애플리케이션 | 대규모 트래픽, 실시간 처리 |
### 반응형 프로그래밍(Reactive Programming)의 기본 개념
웹플럭스를 이해하려면 반응형 프로그래밍의 기본 개념을 알아야 합니다. 반응형 프로그래밍은 데이터 스트림과 이벤트에 반응하며, 비동기적으로 데이터를 처리하는 프로그래밍 패러다임입니다. 전통적인 명령형 프로그래밍이 "어떻게(How)"에 초점을 맞춘다면, 반응형 프로그래밍은 "무엇(What)"에 집중합니다. 즉, 데이터가 발생하거나 변경될 때마다 시스템이 자동으로 반응하도록 설계됩니다.
스프링 웹플럭스에서는 이를 구현하기 위해 **Mono**와 **Flux**라는 두 가지 핵심 클래스를 사용합니다:
- **Mono**: 0 또는 1개의 항목을 발행하는 리액티브 스트림입니다. 예를 들어, 단일 객체를 반환하는 API 호출에 적합합니다.
- **Flux**: 0개 이상의 항목을 발행하는 리액티브 스트림입니다. 리스트나 실시간 데이터 스트리밍에 사용됩니다.
이 두 클래스는 리액티브 스트림의 기반이 되며, 데이터가 준비되면 구독자(Subscriber)가 이를 받아 처리하는 방식으로 동작합니다. 예를 들어, 데이터베이스에서 사용자 정보를 조회한다고 가정하면, Mono는 한 명의 사용자 데이터를, Flux는 여러 사용자 데이터를 스트림으로 제공할 수 있습니다.
### 마무리
스프링 웹플럭스는 단순히 "새로운 프레임워크" 이상의 의미를 가집니다. 이는 변화하는 웹 환경에서 더 나은 성능과 확장성을 제공하려는 스프링 팀의 노력의 결과물입니다. 다음 장에서는 웹플럭스가 왜 필요한지, 어떤 상황에서 빛을 발하는지에 대해 더 깊이 탐구해보겠습니다. 웹플럭스의 세계에 첫발을 내디딘 여러분, 준비되셨나요?
---
이 글은 개념적인 설명에 중점을 두었으며, 이후 장에서 실습과 코드 예제를 통해 내용을 보강할 수 있도록 여지를 남겼습니다. 추가로 수정하거나 보완하고 싶은 부분이 있다면 말씀해주세요!

View File

@@ -0,0 +1,50 @@
아래는 **"스프링부트 웹플럭스 시리즈"**의 **2장: 왜 웹플럭스를 사용하는가?"**에 대한 초안입니다. 1장의 흐름을 이어가며 웹플럭스의 필요성과 실질적인 활용 사례를 중심으로 작성했습니다. 이해하기 쉽고 흥미롭게 읽을 수 있도록 자연스러운 문체를 유지했습니다.
---
## 2. 왜 웹플럭스를 사용하는가?
### 비동기와 논블로킹의 필요성
현대 웹 애플리케이션은 과거와는 비교할 수 없을 만큼 복잡하고 높은 요구사항을 충족해야 합니다. 사용자는 빠른 응답을 기대하고, 서버는 동시에 수천, 수만 명의 요청을 처리해야 합니다. 전통적인 동기 방식의 스프링 MVC는 이런 환경에서 한계를 드러냅니다. 예를 들어, 데이터베이스 쿼리나 외부 API 호출처럼 시간이 오래 걸리는 작업은 스레드를 점유한 채 대기하게 만들고, 이는 스레드 풀 고갈이나 응답 지연으로 이어질 수 있습니다.
여기서 비동기와 논블로킹이 등장합니다. 비동기 처리는 작업이 완료될 때까지 기다리지 않고, 다른 작업을 먼저 처리할 수 있게 해줍니다. 논블로킹은 스레드가 작업을 기다리는 동안 멈추지 않고 다른 요청을 처리할 수 있도록 설계된 방식입니다. 웹플럭스는 이 두 가지를 결합해 자원을 더 효율적으로 사용하며, 더 많은 동시 요청을 감당할 수 있는 구조를 제공합니다.
예를 들어보죠. 전통적인 서블릿 기반 서버에서 100개의 스레드로 100개의 요청을 처리한다고 가정하면, 한 번에 100명까지만 서비스할 수 있습니다. 하지만 웹플럭스는 단일 스레드나 소수의 스레드로도 수천 개의 요청을 처리할 수 있습니다. 이는 마치 레스토랑에서 웨이터 한 명이 여러 테이블을 동시에 관리하는 것과 비슷합니다. 손님(요청)이 음식(응답)을 기다리는 동안, 웨이터(스레드)는 다른 손님을 돕는 셈입니다.
### 웹플럭스의 주요 사용 사례
그렇다면 웹플럭스는 언제 사용하면 좋을까요? 아래는 웹플럭스가 특히 빛을 발하는 몇 가지 대표적인 상황입니다.
1. **대규모 트래픽 처리**
- 스트리밍 서비스(넷플릭스, 유튜브)처럼 수많은 사용자가 동시에 데이터를 요청하는 환경에서, 웹플럭스는 자원을 효율적으로 활용해 높은 처리량을 제공합니다.
2. **실시간 데이터 처리**
- 채팅 애플리케이션이나 주식 거래 시스템처럼 실시간으로 데이터를 주고받아야 하는 경우, 웹플럭스는 논블로킹 방식으로 지연을 최소화합니다.
3. **마이크로서비스 아키텍처**
- 여러 서비스가 서로 비동기적으로 통신해야 하는 환경에서, 웹플럭스는 서비스 간 호출을 효율적으로 처리하며 병목 현상을 줄입니다.
4. **I/O 집약적인 작업**
- 데이터베이스 조회, 파일 업로드, 외부 API 호출 등 시간이 오래 걸리는 작업이 많은 애플리케이션에서 웹플럭스는 스레드 낭비를 줄이고 응답성을 높입니다.
반면, 간단한 CRUD(Create, Read, Update, Delete) 작업만 처리하는 소규모 웹사이트라면 굳이 웹플럭스를 사용할 필요는 없을 수 있습니다. 스프링 MVC로도 충분히 빠르고 유지보수가 쉬울 테니까요. 즉, 웹플럭스는 "모든 상황에 맞는 만능 도구"는 아니지만, 특정 문제에 강력한 해결책을 제시합니다.
### 성능 이점과 한계
웹플럭스의 가장 큰 장점은 **높은 확장성**과 **자원 효율성**입니다. Netty와 같은 논블로킹 서버를 기반으로 작동하며, 리액터(Reactor) 프로젝트를 통해 리액티브 스트림을 구현합니다. 이로 인해 CPU와 메모리를 최대한 활용하면서도 응답 속도를 유지할 수 있습니다. 예를 들어, 벤치마크 테스트에서 웹플럭스는 스프링 MVC보다 동일한 하드웨어에서 더 많은 요청을 처리할 수 있는 것으로 나타났습니다.
하지만 장점만 있는 건 아닙니다. 웹플럭스에는 몇 가지 한계와 고려해야 할 점이 있습니다:
- **학습 곡선**: 반응형 프로그래밍은 기존의 명령형 프로그래밍과 사고방식이 달라 초보자에게는 낯설 수 있습니다.
- **디버깅의 복잡성**: 비동기 흐름을 추적하다 보면 에러가 어디서 발생했는지 파악하기 어려울 때가 있습니다.
- **모든 환경에 적합하지 않음**: CPU 집약적인 작업(예: 복잡한 수학 연산)이 주를 이루는 경우, 논블로킹의 이점이 크지 않을 수 있습니다.
결국 웹플럭스를 선택할 때는 애플리케이션의 요구사항과 팀의 기술 수준을 고려해야 합니다. "최신 기술이니까 써본다"는 접근보다는, 실제로 문제를 해결할 수 있는 도구인지 냉정히 판단하는 게 중요합니다.
### 마무리
웹플럭스는 단순히 트렌드에 편승한 기술이 아니라, 현대 웹 개발의 도전 과제를 해결하기 위한 실용적인 도구입니다. 비동기와 논블로킹을 통해 자원을 아끼고 성능을 끌어올리는 이 프레임워크는, 특히 대규모 시스템에서 그 진가를 발휘합니다. 다음 장에서는 웹플럭스를 직접 사용해보는 첫걸음을 내딛으며, 이론을 실습으로 연결해보겠습니다. 준비되셨죠?
---
이 장은 웹플럭스의 필요성과 실무적 가치를 강조하며, 독자가 "왜 써야 하는지"에 공감할 수 있도록 구체적인 사례와 함께 설명했습니다. 추가로 다루고 싶은 내용이나 조정할 부분이 있다면 말씀해주세요!

View File

@@ -0,0 +1,120 @@
아래는 **"스프링부트 웹플럭스 시리즈"**의 **3장: 스프링부트 웹플럭스 시작하기"**에 대한 초안입니다. Gradle을 사용한 설정 예시와 간략한 코드를 포함하여 실습 중심으로 작성했습니다. 초보자도 따라 할 수 있도록 단계별로 설명하며, 자연스럽고 친근한 문체를 유지했습니다.
---
## 3. 스프링부트 웹플럭스 시작하기
이제 이론을 넘어 직접 손을 움직여볼 시간입니다. 이 장에서는 스프링부트 웹플럭스를 사용해 간단한 애플리케이션을 만들어보며, 환경 설정부터 기본 라우팅까지 경험해보겠습니다. Gradle을 빌드 도구로 사용하며, 코드도 최대한 간결하게 유지하겠습니다. 자, 시작해볼까요?
### 개발 환경 설정 (Gradle 의존성 추가)
먼저, 스프링부트 웹플럭스 프로젝트를 만들려면 필요한 의존성을 추가해야 합니다. 스프링 이니셜라이저(Spring Initializr)를 사용하거나, 기존 프로젝트에 설정을 추가할 수 있습니다. 여기서는 Gradle 기반으로 진행합니다.
`build.gradle` 파일을 열고 아래 내용을 추가하세요:
```gradle
plugins {
id 'org.springframework.boot' version '3.2.4'
id 'io.spring.dependency-management' version '1.1.4'
id 'java'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-webflux'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'io.projectreactor:reactor-test'
}
test {
useJUnitPlatform()
}
```
- `spring-boot-starter-webflux`: 웹플럭스 기본 의존성으로, Netty 서버와 리액터 라이브러리를 포함합니다.
- `spring-boot-starter-test``reactor-test`: 테스트를 위한 의존성입니다.
의존성을 추가한 뒤, 터미널에서 `./gradlew build`를 실행해 의존성이 잘 다운로드되는지 확인하세요.
### 첫 번째 웹플럭스 애플리케이션 만들기
스프링부트는 기본적으로 실행 가능한 애플리케이션을 빠르게 만들 수 있도록 도와줍니다. 프로젝트 루트 패키지에 `Application` 클래스를 생성하고, 아래처럼 설정합니다:
```java
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
```
이제 애플리케이션을 실행해보세요. `./gradlew bootRun` 명령어를 입력하면 기본 포트(8080)에서 서버가 시작됩니다. 아직 아무 기능은 없지만, 웹플럭스 기반 애플리케이션이 구동된 겁니다!
### 기본적인 라우팅과 컨트롤러 설정
이제 간단한 엔드포인트를 추가해보겠습니다. 웹플럭스에서는 두 가지 방식으로 라우팅을 처리할 수 있는데, 여기서는 어노테이션 기반 컨트롤러를 먼저 사용해보겠습니다. 함수형 라우팅은 나중에 다룰게요.
`src/main/java/com/example/demo` 경로에 `HelloController` 클래스를 생성하고, 아래 코드를 추가하세요:
```java
package com.example.demo;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
@RestController
public class HelloController {
@GetMapping("/hello")
public Mono<String> sayHello() {
return Mono.just("Hello, WebFlux!");
}
}
```
- `@RestController`: REST API를 처리하는 컨트롤러임을 나타냅니다.
- `Mono<String>`: 단일 문자열을 비동기적으로 반환합니다.
애플리케이션을 다시 실행한 뒤, 브라우저나 Postman에서 `http://localhost:8080/hello`로 접속해보세요. "Hello, WebFlux!"라는 응답이 나타날 겁니다. 이렇게 간단한 코드로 비동기 엔드포인트를 만들어보았습니다!
혹시 더 실험해보고 싶다면, 아래처럼 약간의 지연을 추가해볼 수도 있습니다:
```java
@GetMapping("/hello-delay")
public Mono<String> sayHelloWithDelay() {
return Mono.just("Hello with delay!")
.delayElement(Duration.ofSeconds(2));
}
```
`/hello-delay`로 접속하면 2초 뒤에 응답이 오는 걸 확인할 수 있습니다. 이건 웹플럭스가 비동기적으로 동작한다는 걸 느끼게 해주는 작은 예제입니다.
### 잠깐 확인하기
지금까지 한 걸 정리해보면:
1. Gradle로 웹플럭스 의존성을 설정했습니다.
2. 기본 애플리케이션을 실행해봤고,
3. 간단한 컨트롤러로 첫 번째 엔드포인트를 만들었습니다.
별로 어렵지 않죠? 스프링부트가 복잡한 설정을 대신 처리해주기 때문에, 우리는 핵심 로직에 집중할 수 있습니다. 웹플럭스는 처음엔 낯설 수 있지만, 이런 작은 성공 경험을 쌓다 보면 금방 익숙해질 겁니다.
### 마무리
이 장에서는 웹플럭스 애플리케이션의 기본 틀을 만들어보며 첫발을 내디뎠습니다. 다음 장에서는 `Mono``Flux`를 더 깊이 파고들며, 리액티브 스트림의 매력을 본격적으로 탐구해보겠습니다. 직접 코드를 작성해본 느낌이 어떠신가요? 다음 단계로 넘어갈 준비가 되셨길 바랍니다!
---
이 글은 실습 위주로 간결하게 작성했으며, 초보자가 따라 하기 쉽도록 Gradle 설정과 코드 예제를 최소화했습니다. 추가 설명이나 다른 예제가 필요하면 말씀해주세요!

View File

@@ -0,0 +1,91 @@
아래는 **"스프링부트 웹플럭스 시리즈"**의 **4장: 리액티브 데이터 스트림 이해"**에 대한 초안입니다. 3장에서의 실습을 바탕으로 `Mono``Flux`를 중심으로 리액티브 스트림의 개념을 설명하고, 간단한 예제를 포함했습니다. 코드도 간략히 유지하며 초보자가 이해하기 쉽게 작성했습니다.
---
## 4. 리액티브 데이터 스트림 이해
3장에서 우리는 첫 번째 웹플럭스 애플리케이션을 만들며 `Mono`를 살짝 맛봤습니다. 이번 장에서는 웹플럭스의 핵심인 리액티브 데이터 스트림을 깊이 파고들어, `Mono``Flux`가 무엇인지, 어떻게 동작하는지 알아보겠습니다. 실습도 곁들이며 개념을 확실히 잡아볼게요. 준비되셨죠?
### Mono와 Flux의 개념
리액티브 프로그래밍의 세계에서 데이터는 "스트림" 형태로 흐릅니다. 이 스트림을 다루는 두 가지 주요 도구가 바로 `Mono``Flux`입니다. 둘 다 스프링 웹플럭스가 사용하는 리액터(Reactor) 라이브러리에서 제공되며, 비동기 데이터 처리를 가능하게 합니다.
- **Mono**: 0개 또는 1개의 항목을 발행하는 스트림입니다. 단일 값을 반환할 때 유용합니다. 예를 들어, 데이터베이스에서 한 명의 사용자 정보를 조회하거나, 외부 API에서 단일 응답을 받을 때 사용합니다.
- **Flux**: 0개 이상의 항목을 발행하는 스트림입니다. 여러 개의 데이터를 순차적으로 처리하거나, 실시간으로 계속 들어오는 데이터를 다룰 때 적합합니다. 리스트나 스트리밍 데이터가 대표적인 예입니다.
쉽게 말해, `Mono`는 "하나 아니면 없음"이고, `Flux`는 "여러 개가 올 수도 있음"이라고 생각하면 됩니다. 이 둘은 리액티브 스트림의 기본 빌딩 블록으로, 데이터를 비동기적으로 생성하고 구독자가 이를 소비하는 구조를 만듭니다.
### 리액티브 스트림의 동작 원리
리액티브 스트림은 "발행-구독(Publish-Subscribe)" 모델을 따릅니다. 데이터가 준비되면 발행자(Publisher)가 이를 발행하고, 구독자(Subscriber)가 그 데이터를 받아 처리합니다. 이 과정에서 중요한 점은 **비동기성**과 **백프레셔(Backpressure)**입니다.
- **비동기성**: 데이터가 준비되는 즉시 처리하지 않고, 준비 완료 시점에 맞춰 반응합니다. 스레드가 블록되지 않으니 자원을 효율적으로 사용할 수 있죠.
- **백프레셔**: 구독자가 데이터 처리 속도를 조절할 수 있게 해줍니다. 예를 들어, 데이터가 너무 빨리 쏟아지면 "천천히 보내줘"라고 요청할 수 있는 셈입니다.
`Mono``Flux`는 이런 원리를 구현한 도구로, 우리가 작성하는 코드는 이 흐름을 정의하는 역할을 합니다.
### 간단한 예제로 배우는 Mono와 Flux
이제 직접 코드를 보며 느낌을 익혀보겠습니다. 3장에서 만든 프로젝트에 새 컨트롤러를 추가하거나, 별도의 클래스에서 테스트해볼 수 있습니다. 여기서는 간단히 컨트롤러로 예제를 작성합니다.
`src/main/java/com/example/demo``StreamController`를 만들고 아래 코드를 추가하세요:
```java
package com.example.demo;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.Duration;
@RestController
public class StreamController {
@GetMapping("/mono")
public Mono<String> getMono() {
return Mono.just("This is a Mono!")
.delayElement(Duration.ofSeconds(1));
}
@GetMapping("/flux")
public Flux<String> getFlux() {
return Flux.just("This", "is", "a", "Flux!")
.delayElements(Duration.ofSeconds(1));
}
}
```
- `/mono` 엔드포인트: `Mono`로 단일 문자열을 1초 지연 후 반환합니다. 접속하면 1초 뒤에 "This is a Mono!"가 표시됩니다.
- `/flux` 엔드포인트: `Flux`로 여러 문자열을 1초 간격으로 순차적으로 발행합니다. 접속하면 "This", "is", "a", "Flux!"가 1초씩 간격을 두고 나타납니다(클라이언트에 따라 한꺼번에 보일 수도 있음).
애플리케이션을 실행하고 `http://localhost:8080/mono``http://localhost:8080/flux`를 브라우저나 Postman으로 테스트해보세요. `Mono`는 한 번에 끝나고, `Flux`는 여러 값을 순차적으로 보내는 걸 느낄 수 있습니다.
더 재미있는 예제로, 숫자 스트림을 만들어볼까요?
```java
@GetMapping("/numbers")
public Flux<Integer> getNumbers() {
return Flux.range(1, 5)
.delayElements(Duration.ofSeconds(1));
}
```
`/numbers`에 접속하면 1부터 5까지 숫자가 1초 간격으로 출력됩니다. 이런 식으로 `Flux`는 연속적인 데이터를 다룰 때 강력합니다.
### 잠깐 정리
- `Mono`: 단일 데이터(0 또는 1개)를 비동기적으로 처리.
- `Flux`: 여러 데이터(0개 이상)를 스트림으로 처리.
- 동작: 발행자가 데이터를 준비하면 구독자가 받아서 처리하며, 비동기와 백프레셔로 효율성을 높임.
이 예제들은 아주 기본적인 수준이지만, 리액티브 스트림의 흐름을 이해하는 데 큰 도움이 됩니다. 실제로는 데이터베이스나 외부 API와 연동할 때 더 빛을 발휘하죠.
### 마무리
`Mono``Flux`는 웹플럭스의 심장과도 같습니다. 이 두 도구를 잘 다룰 수 있다면, 비동기 프로그래밍의 세계가 훨씬 친근하게 느껴질 겁니다. 다음 장에서는 웹플럭스의 라우팅 방식을 더 깊이 탐구하며, 함수형 접근법도 함께 살펴보겠습니다. 이번 장에서 스트림의 맛을 보셨으니, 다음 단계가 더 기대되시죠?
---
이 장은 개념 설명과 간단한 실습을 균형 있게 배치해 초보자가 부담 없이 따라 할 수 있도록 했습니다. 코드도 최소화했으며, 추가 예제나 설명이 필요하면 언제든 말씀해주세요!

View File

@@ -0,0 +1,121 @@
아래는 **"스프링부트 웹플럭스 시리즈"**의 **5장: 웹플럭스에서 라우팅 처리"**에 대한 초안입니다. 4장의 흐름을 이어가며 웹플럭스의 두 가지 라우팅 방식(함수형 라우팅과 어노테이션 기반 컨트롤러)을 비교하고, 실습 예제를 간략히 포함했습니다. 초보자가 이해하기 쉽도록 단계별로 설명하며 자연스러운 문체를 유지했습니다.
---
## 5. 웹플럭스에서 라우팅 처리
지난 장에서 `Mono``Flux`로 리액티브 스트림의 기본을 익혔습니다. 이제 이 스트림을 클라이언트 요청에 연결하는 방법을 배워볼 차례입니다. 웹플럭스에서는 라우팅을 처리하는 두 가지 주요 방식이 있습니다: **함수형 라우팅(RouterFunction)**과 **어노테이션 기반 컨트롤러**. 이번 장에서는 두 접근법을 살펴보고, 각각의 장단점을 비교하며 간단한 예제를 작성해보겠습니다. 자, 시작합시다!
### RouterFunction을 사용한 함수형 라우팅
웹플럭스는 함수형 프로그래밍 스타일을 지원하며, `RouterFunction`을 통해 라우팅을 정의할 수 있습니다. 이 방식은 선언적으로 경로와 핸들러를 연결하며, 코드가 간결하고 유연하다는 장점이 있습니다. 전통적인 컨트롤러보다 더 리액티브 철학에 가까운 접근법이죠.
먼저, 3장에서 만든 프로젝트에 새로운 클래스를 추가합니다. `src/main/java/com/example/demo` 경로에 `RouteConfig` 클래스를 만들고 아래 코드를 작성해보세요:
```java
package com.example.demo;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
@Configuration
public class RouteConfig {
@Bean
public RouterFunction<ServerResponse> routes() {
return route(GET("/functional/hello"),
request -> ServerResponse.ok().bodyValue("Hello from RouterFunction!"));
}
}
```
- `@Configuration`: 스프링 설정 클래스임을 나타냅니다.
- `RouterFunction<ServerResponse>`: 요청 경로와 응답을 정의합니다.
- `route()`: `/functional/hello`로 들어오는 GET 요청에 대해 "Hello from RouterFunction!"를 반환합니다.
애플리케이션을 실행하고 `http://localhost:8080/functional/hello`로 접속해보세요. 간단한 문자열 응답이 표시될 겁니다. 이 방식은 컨트롤러 클래스 없이도 라우팅을 처리할 수 있어, 코드 구조를 간소화할 수 있습니다.
### 어노테이션 기반 컨트롤러와의 비교
4장에서 사용했던 어노테이션 기반 컨트롤러는 스프링 MVC와 비슷한 방식으로 동작합니다. `@RestController`, `@GetMapping` 같은 어노테이션을 사용해 직관적으로 엔드포인트를 정의하죠. 비교를 위해 이전 예제를 다시 보겠습니다:
```java
package com.example.demo;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
@RestController
public class HelloController {
@GetMapping("/hello")
public Mono<String> sayHello() {
return Mono.just("Hello, WebFlux!");
}
}
```
이제 두 방식의 차이를 표로 정리해보겠습니다:
| **특징** | **어노테이션 기반 컨트롤러** | **RouterFunction (함수형)** |
|---------------------|----------------------------------|----------------------------------|
| **문법** | `@GetMapping`, `@PostMapping` 등 | `route()`, `GET()`, `POST()` 등 |
| **구조** | 클래스와 메서드 중심 | 함수와 람다 중심 |
| **익숙함** | MVC 경험자라면 쉽게 적응 | 함수형 프로그래밍에 익숙해야 함 |
| **유연성** | 정해진 구조로 간단히 작성 | 동적 라우팅이나 조건부 처리에 강점 |
| **용도** | 전통적인 REST API에 적합 | 리액티브 철학을 살린 경량 애플리케이션 |
어노테이션 방식은 익숙하고 직관적이어서 초보자에게 추천할 만합니다. 반면, 함수형 라우팅은 더 가볍고 유연하며, 대규모 프로젝트에서 라우팅 로직을 한 곳에 모아 관리하기 좋습니다.
### 요청 처리와 응답 생성 실습
함수형 라우팅으로 조금 더 실습해보죠. 이번엔 요청 파라미터를 받아 응답을 만들어봅시다. `RouteConfig`에 새 경로를 추가합니다:
```java
@Bean
public RouterFunction<ServerResponse> routes() {
return route(GET("/functional/greet/{name}"),
request -> {
String name = request.pathVariable("name");
return ServerResponse.ok().bodyValue("Hello, " + name + "!");
})
.andRoute(GET("/functional/hello"),
request -> ServerResponse.ok().bodyValue("Hello from RouterFunction!"));
}
```
- `/functional/greet/{name}`: URL 경로에서 `name` 변수를 추출해 맞춤형 인사말을 반환합니다.
`http://localhost:8080/functional/greet/Alice`로 접속하면 "Hello, Alice!"가 출력됩니다. 이렇게 함수형 라우팅은 요청 데이터를 쉽게 처리하고, `Mono``Flux`로 응답을 확장할 수도 있습니다.
어노테이션 방식으로 동일한 기능을 구현한다면:
```java
@GetMapping("/greet/{name}")
public Mono<String> greet(@PathVariable String name) {
return Mono.just("Hello, " + name + "!");
}
```
두 방식 모두 결과는 같지만, 코드 스타일과 구조에서 차이가 느껴지죠?
### 어떤 걸 선택해야 할까?
- **어노테이션 기반**: 팀이 MVC에 익숙하거나, 빠르게 REST API를 만들고 싶을 때.
- **함수형 라우팅**: 리액티브의 장점을 극대화하거나, 경량화된 라우팅 로직이 필요할 때.
프로젝트 성격과 팀의 선호도에 따라 선택하면 됩니다. 처음엔 어노테이션 방식으로 시작해, 익숙해지면 함수형으로 넘어가보는 것도 좋은 전략입니다.
### 마무리
이번 장에서 웹플럭스의 라우팅 방식을 두 가지 맛으로 경험해봤습니다. 어노테이션 기반은 친숙하고, 함수형은 새롭고 강력하죠. 다음 장에서는 리액티브 데이터베이스와의 연동을 다루며, 웹플럭스의 실무적인 면모를 더 깊이 탐구해보겠습니다. 라우팅의 맛을 보셨으니, 이제 데이터와 연결해볼 준비 되셨나요?
---
이 장은 두 라우팅 방식의 차이를 명확히 설명하고, 간단한 실습으로 실질적인 이해를 돕도록 구성했습니다. 코드도 최소화했으며, 추가 예제나 조정이 필요하면 말씀해주세요!

View File

@@ -0,0 +1,194 @@
아래는 **"스프링부트 웹플럭스 시리즈"**의 **6장: 리액티브 데이터베이스 연동"**에 대한 초안입니다. MariaDB를 예시로 사용하며, R2DBC를 통해 리액티브 방식으로 연동합니다. 설정은 `application.yaml`을 사용하고, 간단한 실습 코드를 포함했습니다. 초보자도 따라 할 수 있도록 단계별로 설명하며 친근한 문체를 유지했습니다.
---
## 6. 리액티브 데이터베이스 연동
이제 웹플럭스의 리액티브 특성을 데이터베이스와 연결해볼 시간입니다. 이번 장에서는 MariaDB를 리액티브하게 다루는 방법을 배워보겠습니다. 전통적인 JDBC 대신 R2DBC(Reactive Relational Database Connectivity)를 사용하며, 설정은 `application.yaml`로 깔끔하게 정리하겠습니다. 데이터 조회와 저장 예제도 함께 해보며 실습을 진행할게요. 준비되셨죠?
### R2DBC와 리액티브 리포지토리 설정
R2DBC는 관계형 데이터베이스를 비동기/논블로킹 방식으로 접근하게 해주는 드라이버입니다. 웹플럭스와 찰떡궁합이죠. 먼저, MariaDB와 R2DBC를 사용하기 위해 Gradle에 의존성을 추가합니다. `build.gradle`에 아래를 추가하세요:
```gradle
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-r2dbc'
implementation 'io.r2dbc:r2dbc-mariadb:1.1.3'
runtimeOnly 'org.mariadb.jdbc:mariadb-java-client'
}
```
- `spring-boot-starter-data-r2dbc`: R2DBC와 스프링 데이터 통합을 위한 스타터.
- `r2dbc-mariadb`: MariaDB용 R2DBC 드라이버.
의존성을 추가한 뒤, `./gradlew build`로 빌드해줍니다.
다음으로, `application.yaml`을 설정합니다. `src/main/resources``application.yaml` 파일을 만들고 아래 내용을 입력하세요:
```yaml
spring:
r2dbc:
url: r2dbc:mariadb://localhost:3306/webflux_db
username: root
password: yourpassword
data:
r2dbc:
repositories:
enabled: true
```
- `url`: MariaDB 데이터베이스 연결 정보 (예: `webflux_db`라는 데이터베이스 사용).
- `username`, `password`: MariaDB 접속 계정 정보 (환경에 맞게 수정하세요).
MariaDB에 `webflux_db` 데이터베이스를 미리 만들어야 합니다. MariaDB 클라이언트에서 다음 명령어를 실행하세요:
```sql
CREATE DATABASE webflux_db;
```
### MariaDB와의 연동 예제
이제 간단한 사용자(User) 데이터를 저장하고 조회하는 예제를 만들어보겠습니다. 먼저, 테이블을 생성합니다. MariaDB에서 아래 SQL을 실행하세요:
```sql
USE webflux_db;
CREATE TABLE users (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL
);
```
#### 엔티티 클래스 작성
`src/main/java/com/example/demo``User` 클래스를 추가합니다:
```java
package com.example.demo;
import lombok.Data;
import org.springframework.data.annotation.Id;
@Data
public class User {
@Id
private Long id;
private String name;
private String email;
}
```
- `@Id`: 기본 키를 나타냅니다.
- `@Data`: Lombok으로 getter/setter 등을 자동 생성.
#### 리포지토리 인터페이스 생성
`src/main/java/com/example/demo``UserRepository` 인터페이스를 만듭니다:
```java
package com.example.demo;
import org.springframework.data.r2dbc.repository.R2dbcRepository;
import reactor.core.publisher.Mono;
public interface UserRepository extends R2dbcRepository<User, Long> {
Mono<User> findByEmail(String email);
}
```
- `R2dbcRepository`: 리액티브 CRUD 작업을 지원.
- `findByEmail`: 이메일로 사용자 조회 메서드 추가.
#### 데이터 저장 및 조회 실습
컨트롤러를 만들어 데이터를 다뤄봅시다. `src/main/java/com/example/demo``UserController`를 추가합니다:
```java
package com.example.demo;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@RestController
@RequestMapping("/users")
public class UserController {
private final UserRepository userRepository;
public UserController(UserRepository userRepository) {
this.userRepository = userRepository;
}
@PostMapping
public Mono<User> createUser(@RequestBody User user) {
return userRepository.save(user);
}
@GetMapping("/{id}")
public Mono<User> getUserById(@PathVariable Long id) {
return userRepository.findById(id);
}
@GetMapping("/email/{email}")
public Mono<User> getUserByEmail(@PathVariable String email) {
return userRepository.findByEmail(email);
}
@GetMapping
public Flux<User> getAllUsers() {
return userRepository.findAll();
}
}
```
- `createUser`: 새 사용자를 저장.
- `getUserById`: ID로 사용자 조회.
- `getUserByEmail`: 이메일로 사용자 조회.
- `getAllUsers`: 모든 사용자 목록 반환.
#### 테스트해보기
애플리케이션을 실행한 뒤, Postman이나 curl로 테스트해봅시다.
1. **사용자 추가**:
```
POST http://localhost:8080/users
Content-Type: application/json
{"name": "Alice", "email": "alice@example.com"}
```
응답으로 저장된 사용자 객체가 반환됩니다.
2. **사용자 조회 (ID)**:
```
GET http://localhost:8080/users/1
```
ID가 1인 사용자가 반환됩니다.
3. **사용자 조회 (이메일)**:
```
GET http://localhost:8080/users/email/alice@example.com
```
이메일로 조회한 결과가 나옵니다.
4. **모든 사용자 조회**:
```
GET http://localhost:8080/users
```
저장된 모든 사용자가 리스트로 표시됩니다.
### 잠깐 정리
- **R2DBC**: MariaDB를 리액티브하게 연결.
- **`application.yaml`**: 간단한 설정으로 데이터베이스 연결 완료.
- **리포지토리**: 비동기 CRUD 작업을 쉽게 처리.
이 과정에서 `Mono`와 `Flux`가 데이터베이스 작업에 자연스럽게 녹아든 걸 느끼셨을 겁니다. 전통적인 JDBC와 달리 스레드가 블록되지 않아 더 많은 요청을 효율적으로 처리할 수 있죠.
### 마무리
MariaDB와의 리액티브 연동을 성공적으로 해냈습니다! 데이터베이스 작업이 웹플럭스의 비동기 흐름과 어떻게 맞물리는지 실감 나셨나요? 다음 장에서는 이 데이터를 기반으로 REST API를 설계하며, 실무에서 더 유용한 기능을 추가해보겠습니다. 이번 실습이 재미있으셨길 바랍니다!
---
이 장은 MariaDB와 R2DBC를 활용한 실습 중심으로 구성했으며, `application.yaml` 설정과 간단한 코드를 포함했습니다. 추가 예제나 수정이 필요하면 말씀해주세요!

View File

@@ -0,0 +1,171 @@
아래는 **"스프링부트 웹플럭스 시리즈"**의 **7장: 웹플럭스와 REST API 설계"**에 대한 초안입니다. 6장에서 MariaDB와의 연동을 바탕으로 REST API를 설계하며, 상태 코드와 에러 처리를 포함한 실습을 진행합니다. 코드도 간략히 유지하며 초보자가 따라 하기 쉽게 자연스러운 문체로 작성했습니다.
---
## 7. 웹플럭스와 REST API 설계
6장에서 MariaDB와 리액티브하게 연동하며 데이터를 다룰 준비를 마쳤습니다. 이제 이 데이터를 기반으로 실전에서 사용할 수 있는 REST API를 설계해보겠습니다. 단순히 데이터를 주고받는 데 그치지 않고, 상태 코드와 에러 처리까지 추가해 클라이언트와의 비동기 통신을 완성도 있게 구현해볼게요. 시작합시다!
### RESTful 엔드포인트 구현
REST API는 자원을 중심으로 설계되며, HTTP 메서드(GET, POST, PUT, DELETE)를 사용해 CRUD 작업을 처리합니다. 6장의 `UserController`를 확장해 RESTful 방식으로 개선해보겠습니다. 기존 코드를 조금 수정하고 기능을 추가합니다.
`src/main/java/com/example/demo/UserController.java`를 아래처럼 업데이트하세요:
```java
package com.example.demo;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@RestController
@RequestMapping("/users")
public class UserController {
private final UserRepository userRepository;
public UserController(UserRepository userRepository) {
this.userRepository = userRepository;
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Mono<User> createUser(@RequestBody User user) {
return userRepository.save(user);
}
@GetMapping("/{id}")
public Mono<User> getUserById(@PathVariable Long id) {
return userRepository.findById(id);
}
@GetMapping
public Flux<User> getAllUsers() {
return userRepository.findAll();
}
@PutMapping("/{id}")
public Mono<User> updateUser(@PathVariable Long id, @RequestBody User user) {
return userRepository.findById(id)
.flatMap(existingUser -> {
existingUser.setName(user.getName());
existingUser.setEmail(user.getEmail());
return userRepository.save(existingUser);
});
}
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public Mono<Void> deleteUser(@PathVariable Long id) {
return userRepository.deleteById(id);
}
}
```
- `POST /users`: 새 사용자 생성 (201 Created 상태 코드 반환).
- `GET /users/{id}`: ID로 사용자 조회.
- `GET /users`: 모든 사용자 목록 조회.
- `PUT /users/{id}`: 기존 사용자 정보 업데이트.
- `DELETE /users/{id}`: 사용자 삭제 (204 No Content 반환).
이제 API가 RESTful 원칙을 따르며, 각 엔드포인트가 명확한 역할을 수행합니다.
### 상태 코드와 에러 처리
REST API에서 상태 코드는 클라이언트에게 작업 결과를 알려주는 중요한 신호입니다. 하지만 데이터가 없거나 오류가 발생할 때도 적절히 대응해야죠. 예를 들어, 존재하지 않는 사용자를 조회하려 하면 404 Not Found를 반환하도록 해보겠습니다.
`UserController`에 에러 처리를 추가합니다:
```java
@GetMapping("/{id}")
public Mono<User> getUserById(@PathVariable Long id) {
return userRepository.findById(id)
.switchIfEmpty(Mono.error(new UserNotFoundException("User with ID " + id + " not found")));
}
@ExceptionHandler(UserNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public Mono<String> handleUserNotFound(UserNotFoundException ex) {
return Mono.just(ex.getMessage());
}
```
그리고 `UserNotFoundException` 클래스를 새로 만듭니다:
```java
package com.example.demo;
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String message) {
super(message);
}
}
```
- `switchIfEmpty`: 데이터가 없으면 예외를 발생시킵니다.
- `@ExceptionHandler`: 예외를 캐치해 404 상태 코드와 메시지를 반환.
이제 `http://localhost:8080/users/999` (존재하지 않는 ID)로 접속하면 "User with ID 999 not found" 메시지와 404 상태 코드가 반환됩니다.
삭제 시에도 비슷하게 처리할 수 있습니다:
```java
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public Mono<Void> deleteUser(@PathVariable Long id) {
return userRepository.findById(id)
.switchIfEmpty(Mono.error(new UserNotFoundException("User with ID " + id + " not found")))
.flatMap(user -> userRepository.deleteById(id));
}
```
존재하지 않는 ID로 삭제를 시도하면 404가 반환됩니다.
### 클라이언트와의 비동기 통신
웹플럭스의 REST API는 비동기적으로 동작하므로, 클라이언트도 이를 활용할 수 있어야 합니다. 예를 들어, Postman으로 테스트할 때 응답이 바로 오지 않고 스트림 형태로 전달될 수 있습니다. 특히 `Flux`를 반환하는 `/users` 엔드포인트는 여러 데이터를 순차적으로 보냅니다.
클라이언트에서 비동기 요청을 테스트하려면 curl로 해보세요:
```bash
curl -v http://localhost:8080/users
```
데이터가 많다면 스트림처럼 순차적으로 출력되는 걸 볼 수 있습니다. 실제 환경에서는 JavaScript(예: Fetch API나 Axios) 같은 클라이언트 라이브러리로 비동기 호출을 처리할 수 있습니다.
간단한 JavaScript 예제:
```javascript
fetch('http://localhost:8080/users')
.then(response => response.json())
.then(data => console.log(data));
```
`Flux`의 경우 Server-Sent Events(SSE)를 활용하면 실시간 스트리밍도 가능합니다. 이를 위해 컨트롤러에 SSE 지원을 추가할 수 있습니다:
```java
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<User> streamUsers() {
return userRepository.findAll().delayElements(Duration.ofSeconds(1));
}
```
`http://localhost:8080/users/stream`에 접속하면 1초 간격으로 데이터가 스트리밍됩니다.
### 테스트해보기
- **생성**: `POST /users``{"name": "Bob", "email": "bob@example.com"}` 전송 → 201 확인.
- **조회**: `GET /users/1` → Bob의 데이터 확인.
- **업데이트**: `PUT /users/1``{"name": "Bobby", "email": "bobby@example.com"}` → 수정된 데이터 확인.
- **삭제**: `DELETE /users/1` → 204 확인.
- **에러**: `GET /users/999` → 404 확인.
### 마무리
이제 웹플럭스로 완전한 REST API를 설계해봤습니다. 상태 코드와 에러 처리를 추가하며 클라이언트와의 비동기 통신도 자연스럽게 연결했죠. 다음 장에서는 이 API를 테스트하는 방법을 다루며, 웹플럭스 애플리케이션의 안정성을 높이는 법을 배워보겠습니다. REST API 설계의 맛을 느끼셨길 바랍니다!
---
이 장은 REST API의 기본 설계와 실무적인 에러 처리를 다루며, 6장의 데이터베이스 연동을 활용했습니다. 추가 기능이나 조정이 필요하면 말씀해주세요!

View File

@@ -0,0 +1,166 @@
아래는 **"스프링부트 웹플럭스 시리즈"**의 **8장: 웹플럭스에서의 테스트"**에 대한 초안입니다. 7장에서 만든 REST API를 기반으로 `WebTestClient`를 활용한 테스트를 다루며, 단위 테스트와 통합 테스트 예제를 포함했습니다. 코드도 간략히 유지하며 초보자가 따라 하기 쉽게 자연스러운 문체로 작성했습니다.
---
## 8. 웹플럭스에서의 테스트
7장에서 REST API를 완성했으니, 이제 제대로 작동하는지 확인해볼 차례입니다. 웹플럭스는 비동기 특성 때문에 테스트 방식이 기존 스프링 MVC와 조금 다릅니다. 이번 장에서는 `WebTestClient`를 사용해 웹플럭스 애플리케이션을 테스트하는 방법을 배워보겠습니다. 단위 테스트와 통합 테스트를 실습하며, 모킹과 비동기 테스트 팁도 함께 다룰게요. 준비되셨죠?
### WebTestClient를 활용한 테스트 작성
`WebTestClient`는 웹플럭스 애플리케이션의 HTTP 엔드포인트를 테스트하기 위한 도구입니다. 실제 서버를 띄우거나 모킹된 환경에서 테스트할 수 있어 유연합니다. 먼저, 테스트 의존성이 `build.gradle`에 있는지 확인하세요 (3장에서 추가했어야 함):
```gradle
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'io.projectreactor:reactor-test'
```
### 단위 테스트 예제
먼저, `UserController`의 로직을 단위 테스트로 검증해보겠습니다. 데이터베이스 호출을 모킹하여 컨트롤러만 테스트합니다. `src/test/java/com/example/demo``UserControllerTest` 클래스를 만듭니다:
```java
package com.example.demo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.web.reactive.server.WebTestClient;
import reactor.core.publisher.Mono;
import static org.mockito.Mockito.when;
@WebFluxTest(UserController.class)
public class UserControllerTest {
@Autowired
private WebTestClient webTestClient;
@MockBean
private UserRepository userRepository;
@Test
public void testGetUserById() {
User user = new User();
user.setId(1L);
user.setName("Alice");
user.setEmail("alice@example.com");
when(userRepository.findById(1L)).thenReturn(Mono.just(user));
webTestClient.get()
.uri("/users/1")
.exchange()
.expectStatus().isOk()
.expectBody(User.class)
.isEqualTo(user);
}
@Test
public void testGetUserNotFound() {
when(userRepository.findById(999L)).thenReturn(Mono.empty());
webTestClient.get()
.uri("/users/999")
.exchange()
.expectStatus().isNotFound()
.expectBody(String.class)
.isEqualTo("User with ID 999 not found");
}
}
```
- `@WebFluxTest`: 컨트롤러만 테스트하도록 웹플럭스 환경을 설정.
- `@MockBean`: `UserRepository`를 모킹하여 실제 DB 호출을 대체.
- `WebTestClient`: GET 요청을 보내고 응답을 검증.
`testGetUserById`는 성공 케이스, `testGetUserNotFound`는 404 에러 케이스를 테스트합니다. `./gradlew test`로 실행하면 결과가 콘솔에 표시됩니다.
### 통합 테스트 예제
이번엔 실제 데이터베이스와 연동한 통합 테스트를 해보겠습니다. `src/test/java/com/example/demo``UserControllerIntegrationTest`를 추가합니다:
```java
package com.example.demo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.reactive.server.WebTestClient;
import reactor.core.publisher.Mono;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class UserControllerIntegrationTest {
@Autowired
private WebTestClient webTestClient;
@Autowired
private UserRepository userRepository;
@Test
public void testCreateAndGetUser() {
User user = new User();
user.setName("Bob");
user.setEmail("bob@example.com");
webTestClient.post()
.uri("/users")
.body(Mono.just(user), User.class)
.exchange()
.expectStatus().isCreated()
.expectBody(User.class)
.value(savedUser -> {
webTestClient.get()
.uri("/users/" + savedUser.getId())
.exchange()
.expectStatus().isOk()
.expectBody(User.class)
.isEqualTo(savedUser);
});
}
}
```
- `@SpringBootTest`: 전체 애플리케이션 컨텍스트를 로드하며, 랜덤 포트로 서버를 띄움.
- `testCreateAndGetUser`: 사용자를 생성하고, 생성된 ID로 조회까지 테스트.
이 테스트는 실제 MariaDB와 연동되므로, `application.yaml`의 설정이 올바른지 확인하세요.
### 모킹과 비동기 테스트 팁
1. **모킹 활용**: `Mockito`로 리포지토리나 외부 서비스를 모킹하면 의존성을 줄이고 빠르게 테스트할 수 있습니다. 단위 테스트에서 유용하죠.
2. **비동기 처리 확인**: `StepVerifier`를 사용하면 `Mono``Flux`의 비동기 흐름을 더 정밀하게 검증할 수 있습니다. 예를 들어:
```java
@Test
public void testMonoWithStepVerifier() {
User user = new User();
user.setId(1L);
user.setName("Alice");
when(userRepository.findById(1L)).thenReturn(Mono.just(user));
Mono<User> userMono = userRepository.findById(1L);
StepVerifier.create(userMono)
.expectNext(user)
.verifyComplete();
}
```
- `StepVerifier`: 리액티브 스트림의 이벤트를 단계별로 검증.
3. **타임아웃 설정**: 비동기 테스트가 너무 오래 걸리면 실패로 간주하도록 설정하세요. `WebTestClient``.responseTimeout(Duration.ofSeconds(5))`를 추가할 수 있습니다.
### 테스트 실행해보기 _
`./gradlew test`를 실행하면 단위 테스트와 통합 테스트가 모두 실행됩니다. 콘솔에서 성공/실패 여부를 확인할 수 있습니다. 통합 테스트는 MariaDB가 실행 중이어야 하니, 서버가 켜져 있는지 체크하세요.
### 마무리
`WebTestClient`로 웹플럭스 API를 테스트하며, 단위 테스트와 통합 테스트의 차이도 경험해봤습니다. 비동기 환경에서의 테스트는 처음엔 낯설 수 있지만, 익숙해지면 애플리케이션의 안정성을 크게 높일 수 있습니다. 다음 장에서는 성능 최적화와 디버깅을 다루며, 웹플럭스를 실무에서 더 단단하게 다듬는 법을 배워보겠습니다. 테스트 작성의 재미를 느끼셨길 바랍니다!
---
이 장은 `WebTestClient`를 중심으로 실습을 구성했으며, 단위/통합 테스트와 비동기 팁을 간결히 다뤘습니다. 추가 예제나 수정이 필요하면 말씀해주세요!

View File

@@ -0,0 +1,128 @@
아래는 **"스프링부트 웹플럭스 시리즈"**의 **9장: 성능 최적화와 디버깅"**에 대한 초안입니다. 8장에서 테스트를 마무리한 웹플럭스 애플리케이션을 기반으로 성능 분석, 최적화 기법, 그리고 디버깅 방법을 다룹니다. 실습 예제는 간략히 유지하며, 초보자도 이해할 수 있도록 자연스럽고 친근한 문체로 작성했습니다.
---
## 9. 성능 최적화와 디버깅
이제 웹플럭스 애플리케이션이 제법 모양을 갖췄습니다. 하지만 실무에서는 성능과 안정성이 중요하죠. 이번 장에서는 웹플럭스 애플리케이션의 성능을 분석하고 최적화하는 방법을 알아보고, 비동기 환경에서 디버깅하는 팁도 배워보겠습니다. 성능을 쥐어짜고 문제를 해결하는 재미를 느껴보세요. 시작합시다!
### 웹플럭스 애플리케이션의 성능 분석
성능을 최적화하려면 먼저 현재 상태를 파악해야 합니다. 웹플럭스는 비동기/논블로킹으로 작동하니, 병목 지점이 어디인지 찾는 게 핵심입니다. 간단한 방법부터 시작해보죠.
1. **부하 테스트**: `Apache JMeter``wrk` 같은 도구로 애플리케이션에 부하를 줘봅니다. 예를 들어, 7장의 `/users` 엔드포인트를 테스트하려면:
```bash
wrk -t10 -c100 -d30s http://localhost:8080/users
```
- `-t10`: 10개 스레드, `-c100`: 100개 동시 연결, `-d30s`: 30초간 실행.
결과로 초당 요청 수(Requests/sec)와 지연 시간(Latency)을 확인할 수 있습니다.
2. **메트릭 수집**: 스프링 부트 Actuator를 추가해 성능 지표를 모니터링합니다. `build.gradle`에 의존성을 추가하세요:
```gradle
implementation 'org.springframework.boot:spring-boot-starter-actuator'
```
`application.yaml`에 설정 추가:
```yaml
management:
endpoints:
web:
exposure:
include: health,metrics
```
애플리케이션을 실행 후 `http://localhost:8080/actuator/metrics`에 접속하면 요청 처리 시간, 메모리 사용량 등을 볼 수 있습니다.
3. **프로파일링**: VisualVM이나 IntelliJ Profiler로 CPU와 메모리 사용을 분석합니다. 예를 들어, `/users/stream` 엔드포인트가 느리다면 프로파일러로 호출 스택을 확인해보세요.
### 논블로킹 코드 최적화 기법
웹플럭스의 성능은 논블로킹 처리에 달려 있습니다. 몇 가지 최적화 팁을 적용해보죠.
1. **불필요한 블로킹 제거**:
기존 코드에 동기 호출(예: `Thread.sleep`)이 있다면 제거하거나 비동기로 바꿉니다. 예를 들어:
```java
// 비추천
public Mono<String> slowMethod() {
Thread.sleep(1000); // 블로킹
return Mono.just("Done");
}
// 추천
public Mono<String> fastMethod() {
return Mono.just("Done").delayElement(Duration.ofSeconds(1));
}
```
2. **병렬 처리 활용**:
`Flux`로 여러 작업을 병렬로 처리할 수 있습니다. 예를 들어, 사용자 목록을 가져와 외부 API로 추가 데이터를 조회한다고 가정하면:
```java
public Flux<User> enrichUsers() {
return userRepository.findAll()
.parallel()
.runOn(Schedulers.parallel())
.flatMap(user -> fetchAdditionalData(user))
.sequential();
}
```
- `parallel()`과 `Schedulers.parallel()`로 병렬 실행.
3. **캐싱 적용**:
자주 조회되는 데이터는 캐싱으로 부하를 줄입니다. `reactor-cache`를 추가해 구현해보세요:
```gradle
implementation 'io.github.resilience4j:resilience4j-reactor'
```
```java
private final CacheMono<String, User> userCache = CacheMono.from(Caffeine.newBuilder().build());
public Mono<User> getCachedUser(Long id) {
return userCache.lookup(id.toString(), userRepository.findById(id));
}
```
### 리액티브 애플리케이션 디버깅 방법
비동기 코드는 디버깅이 까다로울 수 있습니다. 스택 트레이스가 복잡하고, 문제가 어디서 발생했는지 찾기 어렵죠. 몇 가지 디버깅 팁을 소개합니다.
1. **로그 추가**:
`doOn` 연산자로 흐름을 추적합니다. `UserController`의 `getUserById`에 적용해보죠:
```java
@GetMapping("/{id}")
public Mono<User> getUserById(@PathVariable Long id) {
return userRepository.findById(id)
.doOnSubscribe(sub -> log.info("Fetching user with ID: {}", id))
.doOnNext(user -> log.info("Found user: {}", user))
.doOnError(err -> log.error("Error: ", err))
.switchIfEmpty(Mono.error(new UserNotFoundException("User with ID " + id + " not found")));
}
```
- `log`는 `org.slf4j.Logger`를 사용 (클래스 상단에 선언 필요).
2. **리액터 디버깅 모드**:
`reactor-tools`를 추가해 스택 트레이스를 개선합니다:
```gradle
implementation 'io.projectreactor:reactor-tools'
```
애플리케이션 시작 시 활성화:
```java
public static void main(String[] args) {
Hooks.onOperatorDebug();
SpringApplication.run(Application.class, args);
}
```
에러 발생 시 더 자세한 스택 트레이스가 출력됩니다.
3. **StepVerifier로 확인**:
테스트 중에 디버깅할 때는 `StepVerifier`로 스트림을 단계별로 검증합니다. 8장의 예제를 참고하세요.
### 실습: 성능 개선 확인
`/users` 엔드포인트에 캐싱을 추가하고 부하 테스트로 성능 변화를 확인해보세요:
1. 캐싱 없는 상태에서 `wrk`로 테스트.
2. 캐싱 추가 후 다시 테스트.
3. 결과를 비교하며 응답 시간 감소를 확인.
### 마무리
성능 분석과 최적화로 웹플럭스의 잠재력을 끌어올리고, 디버깅으로 문제를 빠르게 잡아냈습니다. 비동기 환경은 처음엔 어색할 수 있지만, 이런 도구와 기법을 익히면 실무에서 큰 힘이 됩니다. 다음 장에서는 실전 프로젝트로 마이크로서비스를 구축하며, 웹플럭스를 더 큰 무대에 올려보겠습니다. 이번 장에서 애플리케이션이 더 단단해진 느낌이 드시길 바랍니다!
---
이 장은 성능 분석과 디버깅 실무 팁을 중심으로 구성했으며, 간단한 실습으로 이해를 도왔습니다. 추가 예제나 조정이 필요하면 말씀해주세요!

View File

@@ -0,0 +1,189 @@
아래는 **"스프링부트 웹플럭스 시리즈"**의 **10장: 실전 프로젝트: 웹플럭스로 마이크로서비스 구축"**에 대한 초안입니다. 9장까지의 내용을 바탕으로 간단한 마이크로서비스를 설계하고, 웹플럭스와 다른 스프링 모듈을 통합하며 배포와 모니터링 팁을 다룹니다. 실습은 간략히 유지하며 초보자도 따라 할 수 있도록 자연스러운 문체로 작성했습니다.
---
## 10. 실전 프로젝트: 웹플럭스로 마이크로서비스 구축
이제 웹플럭스의 기본기를 실전에서 활용해볼 시간입니다. 이번 장에서는 간단한 마이크로서비스를 설계하고, 웹플럭스를 중심으로 다른 스프링 모듈과 통합하며, 배포와 모니터링까지 다뤄보겠습니다. 작은 프로젝트지만 실무에서 유용한 패턴을 경험할 수 있을 거예요. 준비되셨죠? 출발합시다!
### 간단한 마이크로서비스 설계
마이크로서비스는 독립적으로 배포 가능한 작은 서비스 단위로, 여기서는 두 개의 서비스를 만들어보겠습니다:
1. **User Service**: 사용자 정보를 관리 (6~7장에서 만든 기능 재사용).
2. **Order Service**: 주문 데이터를 처리하며 User Service와 통신.
두 서비스는 REST API로 상호작용하며, 웹플럭스의 비동기 특성을 활용합니다.
#### User Service
기존 프로젝트를 그대로 사용합니다. `UserController``UserRepository`가 이미 준비되어 있죠. 포트를 명확히 하기 위해 `application.yaml`을 수정합니다:
```yaml
server:
port: 8081
spring:
r2dbc:
url: r2dbc:mariadb://localhost:3306/webflux_db
username: root
password: yourpassword
```
#### Order Service
새 프로젝트를 생성하거나, 같은 프로젝트 내에서 별도 패키지로 분리합니다. 여기서는 새 프로젝트로 진행한다고 가정하고, `build.gradle`을 설정합니다:
```gradle
plugins {
id 'org.springframework.boot' version '3.2.4'
id 'java'
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-webflux'
implementation 'org.springframework.boot:spring-boot-starter-data-r2dbc'
implementation 'io.r2dbc:r2dbc-mariadb:1.1.3'
runtimeOnly 'org.mariadb.jdbc:mariadb-java-client'
}
```
`application.yaml` 설정:
```yaml
server:
port: 8082
spring:
r2dbc:
url: r2dbc:mariadb://localhost:3306/webflux_db
username: root
password: yourpassword
```
주문 엔티티와 리포지토리를 추가합니다:
```java
// Order.java
package com.example.order;
import lombok.Data;
import org.springframework.data.annotation.Id;
@Data
public class Order {
@Id
private Long id;
private Long userId;
private String product;
}
// OrderRepository.java
package com.example.order;
import org.springframework.data.r2dbc.repository.R2dbcRepository;
public interface OrderRepository extends R2dbcRepository<Order, Long> {
}
```
Order Service의 컨트롤러는 User Service와 통신합니다:
```java
package com.example.order;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@RestController
@RequestMapping("/orders")
public class OrderController {
private final OrderRepository orderRepository;
private final WebClient webClient;
public OrderController(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
this.webClient = WebClient.create("http://localhost:8081");
}
@PostMapping
public Mono<Order> createOrder(@RequestBody Order order) {
return webClient.get()
.uri("/users/{id}", order.getUserId())
.retrieve()
.bodyToMono(User.class)
.flatMap(user -> orderRepository.save(order));
}
@GetMapping("/user/{userId}")
public Flux<Order> getOrdersByUser(@PathVariable Long userId) {
return orderRepository.findAll()
.filter(order -> order.getUserId().equals(userId));
}
}
```
- `createOrder`: User Service에서 사용자 존재 여부를 확인 후 주문을 저장.
- `getOrdersByUser`: 특정 사용자의 주문 목록 반환.
MariaDB에 `orders` 테이블 생성:
```sql
CREATE TABLE orders (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT NOT NULL,
product VARCHAR(255) NOT NULL
);
```
### 웹플럭스와 다른 스프링 모듈 통합
1. **WebClient**: Order Service에서 User Service와 통신할 때 사용. 비동기 HTTP 클라이언트로, 웹플럭스와 잘 맞습니다.
2. **Actuator**: 모니터링을 위해 추가:
```gradle
implementation 'org.springframework.boot:spring-boot-starter-actuator'
```
```yaml
management:
endpoints:
web:
exposure:
include: health,metrics
```
`http://localhost:8082/actuator/health`로 상태 확인 가능.
3. **스프링 시큐리티** (선택): 간단한 인증을 추가하려면:
```gradle
implementation 'org.springframework.boot:spring-boot-starter-security'
```
기본 사용자/비밀번호로 엔드포인트 보호 가능.
### 배포 및 모니터링 팁
1. **배포**:
- JAR 파일 생성: `./gradlew bootJar`.
- 실행: `java -jar build/libs/order-service-0.0.1-SNAPSHOT.jar`.
- Docker 사용 시:
```dockerfile
FROM openjdk:17-jdk-slim
COPY build/libs/*.jar app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]
```
2. **모니터링**:
- Actuator 메트릭을 Prometheus와 Grafana로 시각화.
- 로그는 SLF4J와 Logback으로 관리하며, `doOn` 연산자로 디버깅 로그 추가.
### 테스트해보기
1. User Service 실행 (`port: 8081`).
2. Order Service 실행 (`port: 8082`).
3. 사용자 생성: `POST http://localhost:8081/users`에 `{"name": "Alice", "email": "alice@example.com"}`.
4. 주문 생성: `POST http://localhost:8082/orders`에 `{"userId": 1, "product": "Book"}`.
5. 주문 조회: `GET http://localhost:8082/orders/user/1`.
### 마무리
간단한 마이크로서비스를 웹플럭스로 구축하며, 서비스 간 통신과 통합의 맛을 봤습니다. 비동기 처리와 논블로킹의 장점을 실무에 적용하는 첫걸음이죠. 다음 장에서는 웹소켓을 다루며 실시간 통신까지 확장해보겠습니다. 이번 프로젝트로 웹플럭스의 실전 감각이 생기셨길 바랍니다!
---
이 장은 마이크로서비스 설계와 통합을 중심으로 실습을 구성했으며, 배포와 모니터링 팁을 간략히 다뤘습니다. 추가 예제나 수정이 필요하면 말씀해주세요!

View File

@@ -0,0 +1,163 @@
아래는 **"스프링부트 웹플럭스 시리즈"**의 **11장: 웹플럭스와 웹소켓"**에 대한 초안입니다. 10장의 마이크로서비스 기반을 활용하여 웹소켓을 통해 실시간 통신을 구현하며, 간단한 채팅 애플리케이션 예제를 포함했습니다. 코드도 간략히 유지하며 초보자가 따라 하기 쉽게 자연스러운 문체로 작성했습니다.
---
## 11. 웹플럭스와 웹소켓
10장에서 마이크로서비스를 구축하며 웹플럭스의 비동기 특성을 실감했습니다. 이번 장에서는 한 단계 더 나아가 웹소켓(WebSocket)을 활용해 실시간 통신을 구현해보겠습니다. 웹플럭스와 웹소켓은 실시간 데이터 전송에 최적화된 조합으로, 채팅 같은 기능을 쉽게 만들 수 있습니다. 간단한 예제를 통해 실습해보죠. 준비되셨나요?
### 실시간 통신을 위한 웹소켓 구현
웹소켓은 클라이언트와 서버 간 양방향 통신을 가능하게 하는 프로토콜입니다. HTTP와 달리 연결을 유지하며, 데이터를 주고받는 데 지연이 적습니다. 웹플럭스는 이를 기본 지원하니, 추가 의존성 없이 바로 시작할 수 있습니다.
10장의 User Service를 확장해 웹소켓 기반 채팅 기능을 추가해보겠습니다. 먼저, 웹소켓 핸들러를 만듭니다.
#### 웹소켓 핸들러 작성
`src/main/java/com/example/demo``ChatWebSocketHandler` 클래스를 추가합니다:
```java
package com.example.demo;
import org.springframework.web.reactive.socket.WebSocketHandler;
import org.springframework.web.reactive.socket.WebSocketSession;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.Duration;
public class ChatWebSocketHandler implements WebSocketHandler {
private final Flux<String> messageFlux;
public ChatWebSocketHandler() {
// 간단한 메시지 스트림 예제
this.messageFlux = Flux.interval(Duration.ofSeconds(1))
.map(i -> "Message " + i);
}
@Override
public Mono<Void> handle(WebSocketSession session) {
// 클라이언트로부터 메시지 수신
Flux<String> input = session.receive()
.map(message -> "Echo: " + message.getPayloadAsText());
// 클라이언트로 메시지 전송
return session.send(messageFlux.mergeWith(input)
.map(session::textMessage));
}
}
```
- `messageFlux`: 서버에서 주기적으로 보내는 메시지 스트림 (예시용).
- `handle`: 클라이언트 메시지를 받아 에코로 반환하고, 서버 메시지도 함께 전송.
#### 웹소켓 라우팅 설정
`src/main/java/com/example/demo``WebSocketConfig`를 추가합니다:
```java
package com.example.demo;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.HandlerMapping;
import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping;
import org.springframework.web.reactive.socket.WebSocketHandler;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class WebSocketConfig {
@Bean
public HandlerMapping webSocketMapping() {
Map<String, WebSocketHandler> map = new HashMap<>();
map.put("/chat", new ChatWebSocketHandler());
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
mapping.setUrlMap(map);
mapping.setOrder(-1); // 다른 매핑보다 우선
return mapping;
}
}
```
- `/chat` 경로로 웹소켓 연결을 처리.
### 웹플럭스와 웹소켓의 조합 예제
User Service (`port: 8081`)에 위 코드를 추가한 뒤 실행합니다. 이제 클라이언트에서 웹소켓 연결을 테스트할 수 있습니다.
#### 클라이언트 테스트
간단한 HTML/JS 클라이언트를 만들어 보죠. `src/main/resources/static``index.html`을 추가합니다:
```html
<!DOCTYPE html>
<html>
<head>
<title>WebSocket Chat</title>
</head>
<body>
<input id="message" type="text">
<button onclick="sendMessage()">Send</button>
<div id="messages"></div>
<script>
const socket = new WebSocket("ws://localhost:8081/chat");
const messagesDiv = document.getElementById("messages");
socket.onmessage = function(event) {
const msg = document.createElement("p");
msg.textContent = event.data;
messagesDiv.appendChild(msg);
};
function sendMessage() {
const input = document.getElementById("message");
socket.send(input.value);
input.value = "";
}
</script>
</body>
</html>
```
`http://localhost:8081/index.html`에 접속하면:
1. 서버에서 1초마다 "Message 0", "Message 1" 등이 표시.
2. 입력창에 메시지를 입력하고 Send 버튼을 누르면 "Echo: [입력값]" 반환.
### 활용 사례 (채팅 애플리케이션 등)
위 예제는 기본적인 에코 서버지만, 이를 확장하면 실시간 채팅 애플리케이션을 만들 수 있습니다:
- **다중 사용자 지원**: `Flux`를 공유 가능한 스트림으로 만들어 모든 클라이언트에 브로드캐스트.
```java
private final Sinks.Many<String> messageSink = Sinks.many().multicast().onBackpressureBuffer();
private final Flux<String> messageFlux = messageSink.asFlux();
@Override
public Mono<Void> handle(WebSocketSession session) {
Flux<String> input = session.receive()
.map(message -> session.getId() + ": " + message.getPayloadAsText())
.doOnNext(messageSink::tryEmitNext);
return session.send(messageFlux.map(session::textMessage));
}
```
- **상태 관리**: Redis나 데이터베이스로 채팅 기록 저장.
- **인증**: 스프링 시큐리티로 웹소켓 연결에 토큰 기반 인증 추가.
채팅 외에도 주식 시세 업데이트, 실시간 알림 등 다양한 활용이 가능합니다.
### 테스트해보기
1. User Service 실행.
2. 브라우저에서 `http://localhost:8081/index.html` 열기.
3. 메시지 입력 후 Send 버튼 클릭 → 서버 메시지와 에코 확인.
### 마무리
웹플럭스와 웹소켓으로 실시간 통신의 세계에 입문했습니다. 비동기 스트림과 양방향 연결이 얼마나 강력한지 느끼셨나요? 다음 장에서는 웹플럭스의 현재와 미래를 돌아보며, 이 기술의 여정을 마무리하겠습니다. 이번 실습으로 실시간 애플리케이션의 가능성을 열어보셨길 바랍니다!
---
이 장은 웹소켓의 기본 구현과 채팅 예제를 중심으로 구성했으며, 실습과 활용 사례를 간략히 다뤘습니다. 추가 기능이나 수정이 필요하면 말씀해주세요!

View File

@@ -0,0 +1,68 @@
아래는 **"스프링부트 웹플럭스 시리즈"**의 **12장: 마무리: 웹플럭스의 현재와 미래"**에 대한 초안입니다. 이 장은 시리즈의 마지막으로, 지금까지 배운 내용을 정리하고 웹플럭스의 현재 트렌드, 커뮤니티 활동, 그리고 추가 학습 로드맵을 다룹니다. 실습은 포함하지 않고, 독자에게 동기를 부여하며 자연스럽고 따뜻한 문체로 작성했습니다.
---
## 12. 마무리: 웹플럭스의 현재와 미래
여기까지 오느라 수고 많으셨습니다! 1장에서 웹플럭스의 개념을 처음 만난 순간부터, 11장에서 실시간 채팅 애플리케이션을 만들기까지, 우리는 리액티브 프로그래밍의 세계를 함께 탐험했습니다. 이번 장에서는 여정을 돌아보고, 웹플럭스가 현재 어디에 서 있는지, 앞으로 어떤 가능성을 열어갈지 이야기해보겠습니다. 그리고 다음 단계로 나아갈 수 있는 길잡이도 드릴게요. 잠시 숨을 고르며 마무리해볼까요?
### 여정 돌아보기
이 시리즈에서 우리는 웹플럭스의 핵심을 단계별로 파헤쳤습니다:
- **기본 개념**: 웹플럭스와 리액티브 프로그래밍의 비동기/논블로킹 철학을 배웠습니다.
- **핵심 도구**: `Mono``Flux`로 데이터 스트림을 다루며, 라우팅과 REST API 설계를 실습했습니다.
- **데이터 연동**: R2DBC와 MariaDB로 리액티브 데이터베이스를 연결했습니다.
- **실전 적용**: 마이크로서비스와 웹소켓으로 실무에 가까운 프로젝트를 구현했습니다.
- **안정성 강화**: 테스트, 성능 최적화, 디버깅으로 애플리케이션을 단단히 다졌습니다.
처음엔 낯설었을 `Mono``Flux`가 이제 제법 친숙해지지 않았나요? 비동기 프로그래밍의 흐름을 이해하고, 이를 실제 코드로 표현한 경험이 여러분의 개발자 여정에 큰 자산이 될 겁니다.
### 스프링 웹플럭스의 최신 트렌드
2025년 현재, 웹플럭스는 스프링 생태계에서 중요한 위치를 차지하고 있습니다. 몇 가지 최신 트렌드를 짚어보면:
- **마이크로서비스의 표준**: 클라우드 네이티브 환경에서 웹플럭스는 높은 확장성과 낮은 자원 소비로 주목받습니다. 쿠버네티스와 결합해 대규모 트래픽을 처리하는 기업들이 늘고 있죠.
- **실시간 애플리케이션 확대**: 웹소켓과 Server-Sent Events(SSE)를 활용한 실시간 기능이 점점 보편화되고 있습니다. 채팅, 알림, 스트리밍 서비스 등에서 웹플럭스의 강점이 두드러집니다.
- **Kotlin과의 시너지**: 코틀린 코루틴(Coroutines)과 웹플럭스의 결합으로 더 간결하고 선언적인 코드 작성이 가능해졌습니다. 코틀린을 배우고 싶다면 웹플럭스와 함께 시작해보는 것도 좋은 선택입니다.
- **AOT 컴파일과 GraalVM**: 스프링 부트 3.x는 GraalVM과의 통합을 강화하며, 웹플럭스 애플리케이션의 시작 시간과 메모리 사용량을 줄이는 방향으로 나아가고 있습니다.
스프링 팀은 웹플럭스를 지속적으로 개선하며, 최신 버전에서 성능 최적화와 개발자 경험(DX)을 강화하고 있습니다. 예를 들어, `spring-boot-starter-webflux`의 최신 업데이트는 더 직관적인 에러 처리와 디버깅 도구를 제공하죠.
### 커뮤니티와 생태계 이야기
웹플럭스는 활발한 커뮤니티의 지원을 받고 있습니다. 몇 가지 참여할 만한 곳을 소개할게요:
- **스프링 공식 블로그**: 최신 릴리스 정보와 튜토리얼을 확인할 수 있습니다. (spring.io/blog)
- **Stack Overflow**: “spring-webflux” 태그로 질문과 답변을 주고받으며 실무 문제를 해결하세요.
- **GitHub**: `spring-projects/spring-boot``reactor/reactor-core` 저장소에서 소스 코드를 살펴보고, 이슈에 참여할 수 있습니다.
- **컨퍼런스**: SpringOne이나 Devoxx 같은 행사에서 웹플럭스 관련 세션을 들으며 최신 트렌드를 접해보세요.
한국에서도 스프링 커뮤니티가 활발히 활동 중입니다. 온라인 포럼이나 오프라인 밋업에서 다른 개발자들과 경험을 나누며 성장할 기회를 찾아보세요.
### 다음 단계로 나아가기 위한 학습 로드맵
웹플럭스의 기초를 다졌으니, 이제 어디로 갈지 궁금하시죠? 몇 가지 추천 경로를 제안드립니다:
1. **심화 학습**:
- 리액터 프로젝트(Reactor): `StepVerifier`, `Sinks` 같은 고급 기능을 익혀보세요.
- 스프링 시큐리티: 웹플럭스에 OAuth2나 JWT를 적용해 보안 강화.
- 그래프QL: REST 대신 GraphQL로 리액티브 API 설계.
2. **확장 프로젝트**:
- 10장의 마이크로서비스를 확장해, 서비스 디스커버리(Eureka)나 API 게이트웨이(Spring Cloud Gateway) 추가.
- 웹소켓으로 더 복잡한 실시간 애플리케이션(예: 멀티플레이어 게임) 구현.
3. **실무 적용**:
- 기존 프로젝트에 웹플럭스를 부분적으로 도입해 성능 비교.
- 모니터링 툴(Prometheus, Grafana)로 운영 환경에서의 성능 분석.
4. **커뮤니티 기여**:
- 오픈소스 프로젝트에 작은 PR을 보내보세요. 문서 번역도 좋은 시작입니다.
- 블로그나 유튜브로 이번 시리즈에서 배운 걸 공유하며 다른 개발자를 도와주세요.
무엇보다 중요한 건 꾸준히 실험하고 질문하는 태도입니다. 리액티브 프로그래밍은 처음엔 복잡하지만, 익숙해질수록 새로운 가능성을 열어줍니다.
### 마무리
웹플럭스는 단순한 기술 이상의 의미를 가집니다. 이는 변화하는 웹 환경에서 더 빠르고 유연한 시스템을 만들려는 도전의 결과물입니다. 여러분은 이 시리즈를 통해 그 도전의 첫걸음을 내디뎠습니다. 이제 여러분만의 프로젝트로 이 기술을 더 깊이 탐구하고, 동료들과 지식을 나누며 성장해보세요. 이 여정이 여러분의 개발자 삶에 새로운 불꽃을 일으키길 바랍니다. 끝까지 함께해줘서 고마워요!
---
이 장은 시리즈의 마무리로서 독자에게 동기를 주고, 실질적인 학습 경로를 제시했습니다. 추가로 다루고 싶은 내용이나 조정이 필요하면 말씀해주세요!

69
docs/webflux/README.md Normal file
View File

@@ -0,0 +1,69 @@
스프링부트 웹플럭스(Spring Boot WebFlux)에 대한 연속적인 글을 위한 목차를 아래에 제안드립니다. 이 목차는 초보자부터 중급 개발자까지 이해할 수 있도록 기초부터 실전까지 다루며, 논리적인 흐름을 유지했습니다. 필요에 따라 주제의 깊이와 순서를 조정할 수 있습니다.
---
### 스프링부트 웹플럭스 시리즈 목차
1. **웹플럭스란 무엇인가?**
- 스프링 웹플럭스 소개
- 기존 스프링 MVC와의 차이점
- 반응형 프로그래밍(Reactive Programming)의 기본 개념
2. **왜 웹플럭스를 사용하는가?**
- 비동기와 논블로킹의 필요성
- 웹플럭스의 주요 사용 사례
- 성능 이점과 한계
3. **스프링부트 웹플럭스 시작하기**
- 개발 환경 설정 (Maven/Gradle 의존성 추가)
- 첫 번째 웹플럭스 애플리케이션 만들기
- 기본적인 라우팅과 컨트롤러 설정
4. **리액티브 데이터 스트림 이해**
- Mono와 Flux의 개념
- 리액티브 스트림의 동작 원리
- 간단한 예제로 배우는 Mono와 Flux
5. **웹플럭스에서 라우팅 처리**
- RouterFunction을 사용한 함수형 라우팅
- 어노테이션 기반 컨트롤러와의 비교
- 요청 처리와 응답 생성 실습
6. **리액티브 데이터베이스 연동**
- R2DBC와 리액티브 리포지토리 설정
- MongoDB, PostgreSQL 등과의 연동 예제
- 데이터 조회 및 저장 실습
7. **웹플럭스와 REST API 설계**
- RESTful 엔드포인트 구현
- 상태 코드와 에러 처리
- 클라이언트와의 비동기 통신
8. **웹플럭스에서의 테스트**
- WebTestClient를 활용한 테스트 작성
- 단위 테스트와 통합 테스트 예제
- 모킹과 비동기 테스트 팁
9. **성능 최적화와 디버깅**
- 웹플럭스 애플리케이션의 성능 분석
- 논블로킹 코드 최적화 기법
- 리액티브 애플리케이션 디버깅 방법
10. **실전 프로젝트: 웹플럭스로 마이크로서비스 구축**
- 간단한 마이크로서비스 설계
- 웹플럭스와 다른 스프링 모듈 통합
- 배포 및 모니터링 팁
11. **웹플럭스와 웹소켓**
- 실시간 통신을 위한 웹소켓 구현
- 웹플럭스와 웹소켓의 조합 예제
- 활용 사례 (채팅 애플리케이션 등)
12. **마무리: 웹플럭스의 현재와 미래**
- 스프링 웹플럭스의 최신 트렌드
- 커뮤니티와 생태계 이야기
- 다음 단계로 나아가기 위한 학습 로드맵
---
이 목차는 웹플럭스의 기초부터 실무 적용까지 단계적으로 다루며, 독자가 자연스럽게 개념을 익히고 실습할 수 있도록 구성했습니다. 특정 주제에 더 깊이 들어가고 싶거나 추가적인 내용을 원하시면 말씀해주세요!