스프링 WebFlux는 웹 애플리케이션을 구축하는 두 가지 주요 모델을 제공하는데, 애노테이션 기반 프로그래밍 모델과 함수형 엔드포인트 프로그래밍 모델이다.
함수형 엔드포인트는 스프링 MVC의 애노테이션 기반 컨트롤러와 달리, 웹 요청을 처리하는 로직을 함수형 프로그래밍 스타일로 정의하는 방식이다.
즉, HandlerFunction과 RouterFunction이라는 두 가지 핵심 인터페이스를 사용하여 요청 라우팅과 처리를 직접 구성한다.
HandlerFunction<T>@FunctionalInterface
public interface HandlerFunction<T extends ServerResponse> {
T handle(ServerRequest request) throws Exception;
}
@RequestMapping메소드와 유사한 역할을 한다.ServerRequest객체를 입력으로 받고, Mono<ServerResponse>를 반환한다.ServerRequest는 HTTP 요청에 대한 모든 정보(헤더, 바디, 쿼리 파라미터 등)를 담고 있다.ServerResponse는 클라이언트에게 보낼 HTTP 응답을 나타낸다.public class MyHandlers {
public Mono<ServerResponse> hello(ServerRequest request) {
return ServerResponse.ok()
.bodyValue("Hello");
}
public Mono<ServerResponse> goodbye(ServerRequest request) {
String name = request.queryParam("name").orElse("Guest");
return ServerResponse.ok()
.bodyValue("Goodbye " + name);
}
}
RouterFunction<T>@FunctionalInterface
public interface RouterFunction<T extends ServerResponse> {
Optional<HandlerFunction<T>> route(ServerRequest request);
default RouterFunction<T> and(RouterFunction<T> other) {
return new RouterFunctions.SameComposedRouterFunction(this, other);
}
default RouterFunction<?> andOther(RouterFunction<?> other) {
return new RouterFunctions.DifferentComposedRouterFunction(this, other);
}
default RouterFunction<T> andRoute(RequestPredicate predicate, HandlerFunction<T> handlerFunction) {
return this.and(RouterFunctions.route(predicate, handlerFunction));
}
default RouterFunction<T> andNest(RequestPredicate predicate, RouterFunction<T> routerFunction) {
return this.and(RouterFunctions.nest(predicate, routerFunction));
}
default <S extends ServerResponse> RouterFunction<S> filter(HandlerFilterFunction<T, S> filterFunction) {
return new RouterFunctions.FilteredRouterFunction(this, filterFunction);
}
default void accept(RouterFunctions.Visitor visitor) {
visitor.unknown(this);
}
default RouterFunction<T> withAttribute(String name, Object value) {
Assert.hasLength(name, "Name must not be empty");
Assert.notNull(value, "Value must not be null");
Map<String, Object> attributes = new LinkedHashMap();
attributes.put(name, value);
return new RouterFunctions.AttributesRouterFunction(this, attributes);
}
default RouterFunction<T> withAttributes(Consumer<Map<String, Object>> attributesConsumer) {
Assert.notNull(attributesConsumer, "AttributesConsumer must not be null");
Map<String, Object> attributes = new LinkedHashMap();
attributesConsumer.accept(attributes);
return new RouterFunctions.AttributesRouterFunction(this, attributes);
}
}
HandlerFunction으로 라우팅(매핑)할지 결정하는 함수이다.@RequestMapping 애노테이션과 유사한 역할을 하지만 더 유연하고 프로그래밍 방식으로 라우팅 규칙을 정의할 수 있다.ServerRequest객체를 입력받고, Mono<HandlerFunction<t>>를 반환한다.Mono.empty()를 반환한다.RouterFunctions.route()팩토리 메소드를 사용하여 정의한다.@Configuration
public class MyRoutingConfiguration {
@Bean
public RouterFunction<ServerResponse> myRoutes(MyHandlers myHandlers) {
return route(GET("/hello"), myHandlers::hello) // GET /hello 요청을 myHandlers.hello 메서드에 매핑
.andRoute(GET("/goodbye").and(queryParam("name", value -> !value.isEmpty())), myHandlers::goodbye) // 값이 비어있지 않은지 검증한다.
.andRoute(POST("/echo"), request -> request.bodyToMono(String.class) // POST /echo 요청
.flatMap(body -> ServerResponse.ok().bodyValue("Echo: " + body)));
}
}
HandlerFunction은 순수 함수에 가깝게 작성될 수 있으므로 단위 테스트가 용이하고 재사용성이 높다.RouterFunction은 요청을 어떤 핸들러롤 보낼지 결정하는 라우팅 역할에만 집중한다.HandlerFunction은 실제 요청을 처리하고 응답을 생성하는 역할에만 집중한다.Mono와 Flux가 함수형/리액티브 패더다임을 따르듯이 함수형 앤드포인트도 이와 일관된 프로그래밍 스타일을 제공한다.HandlerFunction은 일반적인 Java 메소드처럼 테스트할 수 있다.