TL;DR — 인수받은 서버는 Spring WebFlux였지만 데이터 접근은 블로킹 **JPA(Hibernate/JDBC)**였다. WebFlux의 핵심인 논블로킹 I/O 이점은 하나도 못 얻으면서, Mono/Flux의 복잡성과 소수 이벤트루프 스레드가 DB 호출에 막히는 위험만 떠안고 있었다. auth/course/submit 전 모듈을 Spring MVC(서블릿)로 전환하고, 리액티브는 게이트웨이(Spring Cloud Gateway) 경계로만 한정했다.
spring-boot-starter-webflux 기반이었다.repository.findById() 한 번이 DB 응답까지 그 이벤트루프 스레드를 통째로 점유한다.Schedulers.boundedElastic()로 블로킹 호출을 별도 스레드풀로 밀어낼 수도 있지만, 그러면 결국 thread-per-request를 손으로 재현하는 꼴 — 처음부터 MVC(서블릿, 요청당 스레드) 모델이 블로킹 JPA에 자연스럽다.auth → course → submit 순으로 전환하고, common의 리액티브 JWT 빈은 게이트웨이로 이전했다.
1) 의존성
- implementation 'org.springframework.boot:spring-boot-starter-webflux'
+ implementation 'org.springframework.boot:spring-boot-starter-web'
+ implementation 'org.springframework.boot:spring-boot-starter-websocket'
- implementation 'org.springdoc:springdoc-openapi-starter-webflux-ui:2.7.0'
+ implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0'
2) 필터 · 시큐리티: WebFilter(ServerWebExchange/Mono) → OncePerRequestFilter(HttpServletRequest/FilterChain)
- public class IpAddressFilter implements WebFilter {
- public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) { ... }
+ public class IpAddressFilter extends OncePerRequestFilter {
+ protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) { ... }