| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 |
- ArrayList
- 스프링부트
- HashMap
- 테스트자동화
- list
- string
- Visual Studio Code
- Java
- 자바스크립트
- vscode
- 배열
- java테스트
- IntelliJ
- javascript
- junit5
- Eclipse
- 자바
- input
- math
- 자바문법
- html
- CSS
- Array
- 정규식
- js
- 단위테스트
- 인텔리제이
- SpringBoot
- junit
- 문자열
- Today
- Total
어제 오늘 내일
[Reactor] "List랑 뭐가 달라요?" Mono와 Flux, 0개 1개 그리고 N개 본문
MVC에서 List<User>를 리턴하면 "유저 목록이구나", User를 리턴하면 "단건 조회구나"라고 바로 알 수 있죠.
WebFlux에서도 마찬가지입니다. 리턴 타입만 봐도 "데이터가 몇 개 흐를지" 예측할 수 있어야 합니다.
WebFlux의 기반 라이브러리인 Project Reactor는 데이터를 담는 그릇을 딱 두 가지로 정의했습니다.
바로 Mono(모노)와 Flux(플럭스)입니다.
1. Flux [0..N]: 끝없는 데이터의 강물
Flux는 0개에서 N개(무한대 포함)의 데이터를 발행할 수 있는 Publisher입니다.
- 비유: 컨베이어 벨트. 물건이 계속해서 지나갑니다.
- 유사한 개념:
List<T>,Stream<T>,Iterable<T> - 용도:
findAll(): DB에서 여러 건 조회- 실시간 주식 시세 (끝없이 데이터가 들어옴)
- 로그 스트림
// Flux 생성 예시
Flux<String> fruitFlux = Flux.just("Apple", "Banana", "Cherry");
Flux<Integer> rangeFlux = Flux.range(1, 5); // 1, 2, 3, 4, 5
Flux<String> fromList = Flux.fromIterable(Arrays.asList("A", "B", "C"));
2. Mono [0..1]: 딱 하나, 혹은 없음
Mono는 0개 또는 1개의 데이터만 발행하고 종료되는 Publisher입니다.
- 비유: 택배 상자. 열면 물건이 하나 있거나, 비어있거나(Empty).
- 유사한 개념:
Optional<T>,T(단일 객체) - 용도:
findById(): ID로 단건 조회save(): 저장 후 결과 반환 (성공/실패)count(): 개수 세기 (결과는 숫자 하나니까)HttpClient.get(): API 호출 결과 (응답은 한 번 오니까)
// Mono 생성 예시
Mono<String> helloMono = Mono.just("Hello World");
Mono<String> emptyMono = Mono.empty(); // 데이터 없음 (Void)
Mono<String> errorMono = Mono.error(new RuntimeException("에러 발생!"));
3. 왜 굳이 나눴을까요? (Flux 하나로 다 하면 안 돼?)
사실 Flux로도 1개짜리 데이터를 보낼 수 있습니다. (Flux.just("One"))
하지만 의미(Semantics)가 다릅니다.
Mono<User> findById(1L): "이 메서드는 유저를 1명만 찾거나 못 찾습니다." (명확함)Flux<User> findById(1L): "이 메서드는 유저를 여러 명 찾을 수도 있습니다." (오해의 소지)
리턴 타입을 분리함으로써, 개발자는 API 명세서 없이도 코드만 보고 데이터의 개수(Cardinality)를 유추할 수 있게 됩니다.
4. 가장 중요한 규칙: "구독하기 전까진 아무 일도 안 일어난다"
초보자가 가장 많이 하는 실수입니다.
아래 코드를 실행하면 콘솔에 무엇이 찍힐까요?
Flux<String> flux = Flux.just("A", "B", "C");
flux.map(s -> s.toLowerCase()); // 소문자로 변환
// 끝.
정답: 아무것도 안 찍힙니다.
심지어 내부적으로 연산(toLowerCase)조차 실행되지 않습니다.
리액티브 스트림즈는 게으릅니다(Lazy Evaluation).
누군가가 subscribe() (구독)를 해야만 비로소 수도꼭지를 틀고 데이터를 흘려보내기 시작합니다.
Flux<String> flux = Flux.just("A", "B", "C");
flux.subscribe(data -> System.out.println(data));
// 출력:
// A
// B
// C
주의: WebFlux 컨트롤러(
@RestController)에서는 우리가 직접subscribe()를 호출하지 않습니다. 리턴 타입으로Mono나Flux를 던져주면, 스프링 프레임워크가 알아서 구독해 줍니다.
5. Mono와 Flux는 변환 가능합니다
개발하다 보면 1개를 여러 개로, 여러 개를 1개로 바꿀 일이 생깁니다.
- Flux -> Mono:
flux.next(): 첫 번째 데이터만 가져와서 Mono로 변환.flux.collectList(): 데이터를 다 모아서Mono<List<T>>로 변환.
- Mono -> Flux:
mono.flux(): 그냥 Flux로 취급.mono.flatMapMany(): Mono 안의 내용을 펴서 Flux로 변환.
마치며
오늘의 결론입니다.
- Mono: 0~1개의 데이터. (
Optional과 비슷) -> 단건 조회, 저장 - Flux: 0~N개의 데이터. (
List와 비슷) -> 목록 조회, 스트림 - Lazy:
subscribe()하기 전까지는 코드가 실행되지 않는다. (설계도일 뿐!)
이제 데이터를 담는 그릇은 준비되었습니다. 하지만 그릇에 담긴 데이터를 가공(변환, 필터링)해야 쓸모가 있겠죠?
다음 포스팅에서는 "map과 flatMap, 도대체 뭐가 다른가요?" 리액티브 프로그래밍의 마법 같은 연산자(Operator)들을 정복해 보겠습니다. (여기서부터 진짜 코딩입니다!)
도움이 되셨다면 좋아요와 댓글 부탁드립니다! 😊
'IT > SpringBoot' 카테고리의 다른 글
| [Spring WebFlux] 어노테이션 기반 컨트롤러: MVC 개발자라면 10분 만에 적응 가능! (0) | 2026.04.01 |
|---|---|
| [Reactor] "map과 flatMap, 도대체 뭐가 다른가요?" 마법의 연산자 정복하기 (0) | 2026.04.01 |
| [Reactive] "데이터가 강물처럼 흐른다?" 리액티브 스트림즈(Reactive Streams)와 Backpressure 완벽 이해 (0) | 2026.03.31 |
| [Spring WebFlux] "MVC랑 뭐가 달라요?" 블로킹(Blocking) vs 논블로킹(Non-Blocking) 완벽 이해 (0) | 2026.03.30 |
| [Spring Boot] "새로고침 없이 알림이 오네요?" WebSocket과 STOMP로 실시간 채팅방 만들기 (0) | 2026.03.30 |
