컨트롤러 관점에서 Spring MVC vs Spring WebFlux
1. Spring MVC Controller
- @RequestBody 애너테이션을 지정해서 클라이언트의 요청 데이터를 전달받고, ResponseEntity 클래스를 이용해 응답 데이터를 클라이언트에게 전달하는 전형적인 Spring MVC 기반 Controller
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.springframework.beans.factory.annotation.Autowired; | |
import org.springframework.http.ResponseEntity; | |
import org.springframework.web.bind.annotation.*; | |
import java.util.List; | |
@RestController | |
@RequestMapping("/items") | |
public class ItemController { | |
@Autowired | |
private ItemService itemService; | |
@GetMapping | |
public ResponseEntity<List<Item>> getAllItems() { | |
List<Item> items = itemService.getAllItems(); | |
return ResponseEntity.ok(items); | |
} | |
@GetMapping("/{id}") | |
public ResponseEntity<Item> getItemById(@PathVariable("id") Long id) { | |
Item item = itemService.getItemById(id); | |
if (item != null) { | |
return ResponseEntity.ok(item); | |
} else { | |
return ResponseEntity.notFound().build(); | |
} | |
} | |
@PostMapping | |
public ResponseEntity<Item> createItem(@RequestBody Item item) { | |
Item savedItem = itemService.saveItem(item); | |
return ResponseEntity.ok(savedItem); | |
} | |
@PutMapping("/{id}") | |
public ResponseEntity<Item> updateItem(@PathVariable("id") Long id, @RequestBody Item item) { | |
Item updatedItem = itemService.updateItem(id, item); | |
if (updatedItem != null) { | |
return ResponseEntity.ok(updatedItem); | |
} else { | |
return ResponseEntity.notFound().build(); | |
} | |
} | |
@DeleteMapping("/{id}") | |
public ResponseEntity<Void> deleteItem(@PathVariable("id") Long id) { | |
boolean deleted = itemService.deleteItem(id); | |
if (deleted) { | |
return ResponseEntity.noContent().build(); | |
} else { | |
return ResponseEntity.notFound().build(); | |
} | |
} | |
} |
2. Spring WebFlux Controller
- 겉보기에는 단순히 Mono와 Flux로 wrapping 한 것처럼 보이지만 non-blocking을 지원하는 리액티브 핸들러
- 애너테이션 기반의 프로그래밍 모델은 기존의 Spring MVC 구조와 거의 흡사하게 사용할 수 있음
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.springframework.beans.factory.annotation.Autowired; | |
import org.springframework.http.ResponseEntity; | |
import org.springframework.web.bind.annotation.*; | |
import reactor.core.publisher.Flux; | |
import reactor.core.publisher.Mono; | |
@RestController | |
@RequestMapping("/items") | |
public class ItemController { | |
@Autowired | |
private ItemService itemService; | |
@GetMapping | |
public Flux<Item> getAllItems() { | |
return itemService.getAllItems(); | |
} | |
@GetMapping("/{id}") | |
public Mono<ResponseEntity<Item>> getItemById(@PathVariable("id") Long id) { | |
return itemService.getItemById(id) | |
.map(item -> ResponseEntity.ok(item)) | |
.defaultIfEmpty(ResponseEntity.notFound().build()); | |
} | |
@PostMapping | |
public Mono<ResponseEntity<Item>> createItem(@RequestBody Item item) { | |
return itemService.saveItem(item) | |
.map(savedItem -> ResponseEntity.ok(savedItem)); | |
} | |
@PutMapping("/{id}") | |
public Mono<ResponseEntity<Item>> updateItem(@PathVariable("id") Long id, @RequestBody Item item) { | |
return itemService.updateItem(id, item) | |
.map(updatedItem -> ResponseEntity.ok(updatedItem)) | |
.defaultIfEmpty(ResponseEntity.notFound().build()); | |
} | |
@DeleteMapping("/{id}") | |
public Mono<ResponseEntity<Void>> deleteItem(@PathVariable("id") Long id) { | |
return itemService.deleteItem(id) | |
.map(deleted -> ResponseEntity.noContent().build()) | |
.defaultIfEmpty(ResponseEntity.notFound().build()); | |
} | |
} |
Spring WebFlux 기반의 컨트롤러 사용 시 주의할 점
1. Repository/Service 계층에서 블로킹 메서드 사용 여부 확인
- Spring WebFlux에서 논블로킹 방식으로 동작하려면, Repository나 Service 계층에서도 논블로킹 방식으로 동작해야 함
- 즉, Spring Data R2DBC, Spring Data Reactive MongoDB, Spring Data Cassandra 등 non-blocking 리액티브 라이브러리를 사용해야 함
- ex) JPA 기반의 Repository 메서드는 기본적으로 blocking 이기 때문에, Spring WebFlux 환경에서는 적합하지 않으며 대신, R2DBC 또는 리액티브 MongoDB를 사용하는 것이 좋음
2. 블로킹 I/O 사용 여부 확인
- 파일 읽기/쓰기, 네트워크 호출, 데이터베이스 쿼리 등에서 블로킹 I/O가 사용되지 않도록 해야 함
- 이러한 작업은 WebFlux에서 제공하는 non-blocking 방식의 API를 사용해야 함
- 만약 blocking 작업이 필요한 경우, 별도의 쓰레드 풀에서 해당 작업을 처리하도록 Schedulers.boundedElastic() 등을 사용하여 분리해야 함
3. 블로킹 메서드 호출 여부 확인
- Thread.sleep(), blockingQueue.take(), blockingQueue.put() 등의 blocking 메서드를 호출하지 않아야 함
- 위 메서드들은 논블로킹 방식의 처리 흐름을 방해할 수 있음
4. Mono/Flux의 block() 메서드 사용 금지
- WebFlux에서는 Mono.block() 또는 Flux.block()과 같은 메서드를 호출하지 않도록 주의해야 함
- 위 메서드들은 비동기 흐름을 동기식으로 변환하며, blocking 동작을 일으킴
참고
- 스프링으로 시작하는 리액티브 프로그래밍 (황정식 저자)
반응형
'Spring > 스프링으로 시작하는 리액티브 프로그래밍' 카테고리의 다른 글
Spring Data R2DBC 간단 정리 (0) | 2024.09.11 |
---|---|
[Spring WebFlux] 함수형 엔드포인트(Functional Endpoint) (0) | 2024.09.06 |
[Reactor] 자주 사용되는 Operator (0) | 2024.08.11 |
[Spring] Spring WebFlux 개요 (0) | 2024.08.06 |
[Reactor] Reactor 테스트 (0) | 2024.07.31 |