Model-View-Controller (MVC):
Synchronous Processing:
Controller:
@Controller와 @RequestMapping 어노테이션을 사용하여 정의됩니다.Model:
Model이나 ModelMap을 사용하여 데이터를 전달합니다.View:
DispatcherServlet:
HandlerMapping:
ViewResolver:
다음은 Spring MVC 애플리케이션의 일반적인 구성도입니다:
┌────────────┐ ┌───────────────────┐ ┌───────────────┐ ┌──────────────┐
│ Client │ ────▶│ DispatcherServlet │ ────▶ │ Controller │ ────▶│ Service │
└────────────┘ └───────────────────┘ └───────────────┘ └──────────────┘
│ │
▼ ▼
┌────────────┐ ┌──────────────┐
│ Model │ │ Database │
└────────────┘ └──────────────┘
│
▼
┌────────────┐
│ View │
└────────────┘
Node.js + Express 등의 비동기 서버 강세로, 비동기 논 블로킹의 효율성이 대두되며, 스프링에서도 Webflux 도입으로 트렌드를 따라가는 것으로 보입니다.(필자 생각)
다만 주의해야 할 점은, Webflux는 잘못 설계할 경우 동기+비동기 처리가 섞이면서 효율성이 떨어질 수 있습니다. 따라서, 가파른 학습곡선이 요구됩니다.
Reactive Programming:
Non-blocking I/O:
Reactive Streams:
Publisher와 Subscriber:
Router & Handler:
@RestController와 @RequestMapping 어노테이션을 사용.WebClient:
RestTemplate의 대체로 사용됩니다. (보통 API 서버로서 동작하기 때문에 View 가 없습니다.)DispatcherHandler:
HandlerMapping:
HandlerAdapter:
다음은 Spring WebFlux 애플리케이션의 일반적인 구성도입니다:
┌────────────┐ ┌───────────┐ ┌─────────────┐ ┌──────────────┐
│ Client │ ────▶│ Router │ ────▶│ Handler │ ────▶│ Service │
└────────────┘ └───────────┘ └─────────────┘ └──────────────┘
│ │
▼ ▼
┌───────────┐ ┌────────────┐
│ WebClient │ │ Database │
└───────────┘ └────────────┘
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
// 컨트롤러를 사용하는 전통적인 방식
@RestController
@RequestMapping("/users")
public class UserController {
@GetMapping("/{id}")
public Mono<String> getUserById(@PathVariable String id) {
// Simulate a service call
return Mono.just("User " + id);
}
}
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 UserHandler {
public Mono<ServerResponse> getUserById(ServerRequest request) {
String userId = request.pathVariable("id");
return ServerResponse.ok().body(Mono.just("User " + userId), String.class);
}
}
// ---------------- 별도의 파일 -------------------
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.RouterFunctions;
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.RequestPredicates.path;
@Configuration
public class UserRouter {
@Bean
public RouterFunction<ServerResponse> route(UserHandler userHandler) {
return RouterFunctions
.nest(path("/users"),
RouterFunctions.route(GET("/{id}"), userHandler::getUserById));
}
}