개발/Spring

[Spring] Spring5 WebFlux 란

hojak99 2018. 4. 5. 12:44

Spring WebFlux framework

예전에는 Spring-Web-Reactive 였으며, Spring 5 의 메인테마는 JDK 9였는데 이제는 WebFlux 로 바뀌었음.

Reactive System

복수개의 서비스로 이루어진 분산 시스템이 정상 상황 뿐만 아니라 장애 상황에서도 일관된 동작을 보장해주는 시스템이며 Microservice 가 지향하는 방향이다.

용도

  • 비동기-논블록킹 리엑티브 개발에 사용
  • 효율적으로 동작하는 고성능 웹 어플리케이션 개발
  • 서비스 간 호출이 많은 마이크로서비스 아키텍처에 적합

2가지 개발방식 지원

  • 기존의 @MVC 방식 (@Controller, @RestController, @RequestMapping)
  • 새로운 함수형 모델 (RouterFuction, HandlerFuction.)

새로운 요청 - 응답 모델

  • 서블릿 스택과 API 에서 탈피 (서블릿 API 는 리엑티브 함수형 스타일에 적합하지 않음 - HttpServletRequest, HttpServletResponse)
  • ServerRequest, ServerResponse

지원 웹 서버 / 컨테이너

  • Servlet 3.1 + (Tomcat, Jetty, ... - 서블릿 3.1+ d의 비동기-논블럭킹 요청 처리 가능)
  • Netty (비동기-논블럭킹 IO 웹서버)
  • Undertow (비동기-논블럭킹 IO 웹서버)

새로운 함수형 모델을 이용한 WebFlux

@FunctionalInterface
public interface RouterFunction<T extends ServerResponse> {

    /**
     *  ServerRequest request : WebFlux 버전의 웹 요청  
     *  Mono<HandlerFuction<T>> : ServerResponse 를 리턴하는 HandlerFuction (웹 응답을 리턴하는 함수)
     */
    Mono<HandlerFunction<T>> route(ServerRequest request);
}

HandlerFunction

  • 함수형 스타일의 웹 핸들러 (컨트롤러 메소드)
  • 웹 요청을 받아 웹 응답을 돌려주는 함수

함수형 WebFlux 가 웹 요청을 처리하는 방식

  • 요청 매핑 : RouterFuction - url 같은 정보로 어떤 핸들러가 이 요청을 처리할지 결정함
  • 요청 바인딩 -------------------- HandlerFunction
  • 핸들러 실행 -------------------- HandlerFunction
  • 핸들러 결과 처리(응답 생성) ----- HandlerFunction

WebFlux 함수형 Hello/{name} 작성

  • 함수 2개 만들면 됨 (HandlerFuction 먼저 만들고, RouterFunction 에서 path 기준 매핑을 해준다)
// 기존 Spring mvc 구조
@RestConroller
public class MyController {
    
    // RotuerFunction 으로.
    @GetMapping("/hello/{name}")   
    
    // HandlerFuction 으로.
    String hello(@PathVariable String name) {       
        return "Hello "+ name;             
    }
}


/**
 * WebFlux 함수형 
 *
 * HandlerFuction 은 FunctionalInterface 를 구현하는 것이기 때문에 람다식으로 작성한다. 
 * HandlerFunction.handle() 의 람다식 - Mono<T> handle(ServerRequest request);
/*
HandlerFuction helloHandler = req -> {
    String name = req.pathVariable("name");        // ServerRequest.pathVariable() 로 {name} 추출
    Mono<String> result = Mono.just("Hello "+ name);    // 로직 적용 후 결과 값을 Mono 에 담는다 (결과를 리턴할 때 꼭 Mono에 담아서 리턴해야 한다)

    /**
     *  웹 응답을 ServerResponse 로 만든다.
     *  HTTP 응답에는 응답코드, 헤더, 바디가 필요.
     *  ServerResponse 의 빌더를 활용
     *  Mono 에 담긴 ServerResponse 타입으로 리턴
     */
    Mono<ServerResponse> res = ServerResponse.ok().body(result, String.class);      

    return res;
}

/**
 *  WebFlux 함수형 위의 코드를 간단하게 작성 할 시. 
 *  BodyInserters.fromObject() 의 도움을 받아 Mono 에 담는다
 *  로직 결과 값을 바디에 담고 상태코드를 추가해 웹 응답으로 만드는데 content-type 이나 기타 헤더는 스프링에서 알아서 만들어준다.
 */
HandlerFuction helloHandler = req -> 
    ok().body(fromObject("Hello"+req.pathVariable("name")));
/**
 *  이 전체 코드가 RouterFunction.route() 의 람다식 - Mono<HandlerFunction<T>> route(ServerRequest request); 
 */
RouterFunction router = req -> 
    RequestPredicates.path("/hello/{name}").test(req) ?     // 웹 요청 정보 중에서 URL 경로 패턴 검사
        Mono.just(helloHandler) : Mono.empty();     
        // 조건에 맞으면 핸들러 함수를 Mono 에 담아서 반환, 조건에 맞지 않으면 빈 Mono 반환

WebFlux 함수형 Hello/{name} 작성 (HandlerFucntion 과 RouterFuction을 한 번에)

  • RouterFunctions.route(predicate, handler)
Routerfunctions.route(RequestPredicates.path("/hello/{name}"),
    req -> ServerResponse.ok().
        body(fromObject("Hello" + req.pathVariable("name"))));

RotuerFunction 의 등록

  • RouterFunction 타입의 @Bean 으로 만든다.
@Bean
RouterFunction helloPathVarRouter() {
    return route(RequestPredicates.path("/hello/{name}"), 
        req -> ok().body(fromObject("Hello" + req.pathVariable("name"))));
}

위의 handler 로직이 복잡해졌을 때 분리시켜야 한다.

  • handler 코드만 따로 선언한다.
  • 메소드를 정의하고 메소드 참조로 가져온다.
// 다음과 같이 분리시키는 것이 좋다.  
HandlerFunction handler = req -> {
    String res = myService.hello(req.pathVariable("name"));
    return ok().body(fromObject(res));
}

return route(path("/hello/{name}"), handler);
/**
 *  람다식은 메소드 타입(파라미터 타입, 리턴 타입, 예외 던지는 것)이 일치하면 일반 메소드와 호환해서 사용할 수 있다. (메소드 레퍼런스)
 *  다음의 코드는 위에서 분리시킨 handler() 메소드가 같은 의미인 코드이다. 
 *  아래와 같이 클래스 안에 메소드로 작성한 뒤, 메소드 타입이 일치한다면 아래의 코드와 같이 router function 을 정의할 때 사용할 수 있다.
 *  이 장점은 하나의 컴포넌트 안에 메소드 형태로 여러 개를 만들어놓고 사용하면 편하다. 
 */

@Component
public class HelloHandler {
    @Autowired MyService myService;

    Mono<ServerResponse> hello(ServerRequest req) {
        String res = myService.hello(req.pathVariable("name"));
        return ok().body(fromObject(res));
    }

    ....
}

@Bean
RouterFunction helloRouter(@Autowired HelloHandler helloHandler) {
    return route(path("/hello/{name}"), helloHandler::hello);
}


반응형