Spring 5의 Functional Web Framework 소개

1. 소개

Spring WebFlux는 반응 원리를 사용하여 구축 된 새로운 기능적 웹 프레임 워크입니다.

이 자습서에서는 실제로 작업하는 방법을 배웁니다.

이것은 Spring 5 WebFlux에 대한 기존 가이드를 기반으로합니다. 이 가이드에서는 주석 기반 구성 요소를 사용하여 간단한 반응 형 REST 애플리케이션을 만들었습니다. 여기서는 대신 기능적 프레임 워크를 사용합니다.

2. Maven 종속성

이전 기사에서 정의한 것과 동일한 spring-boot-starter-webflux 종속성 이 필요합니다 .

 org.springframework.boot spring-boot-starter-webflux 2.2.6.RELEASE 

3. 기능적인 웹 프레임 워크

기능적 웹 프레임 워크는 요청을 라우팅하고 처리하는 함수를 사용하는 새로운 프로그래밍 모델을 도입합니다.

어노테이션 매핑을 사용하는 어노테이션 기반 모델 과 달리 여기서는 HandlerFunctionRouterFunction을 사용 합니다.

마찬가지로 주석이 달린 컨트롤러에서와 마찬가지로 기능적 엔드 포인트 접근 방식은 동일한 반응 스택에 구축됩니다.

3.1. HandlerFunction

HandlerFunction는 그들에게 라우팅 요청에 대한 응답을 생성하는 기능을 나타냅니다 :

@FunctionalInterface public interface HandlerFunction { Mono handle(ServerRequest request); }

이 인터페이스는 주로 함수입니다. , 이는 서블릿과 매우 유사하게 작동합니다.

표준 Servlet # service (ServletRequest req, ServletResponse res)비교할 때 HandlerFunction 은 응답을 입력 매개 변수로 사용하지 않습니다.

3.2. 라우터 기능

RouterFunction@RequestMapping 주석 의 대안으로 사용됩니다 . 이를 사용하여 요청을 핸들러 함수로 라우팅 할 수 있습니다.

@FunctionalInterface public interface RouterFunction { Mono
    
      route(ServerRequest request); // ... }
    

일반적으로 전체 라우터 함수를 작성하는 대신 도우미 함수 RouterFunctions.route ()가져와 경로를 만들 수 있습니다.

RequestPredicate 를 적용하여 요청을 라우팅 할 수 있습니다 . 조건자가 일치하면 두 번째 인수 인 핸들러 함수가 반환됩니다.

public static  RouterFunction route( RequestPredicate predicate, HandlerFunction handlerFunction)

때문에 경로 () 메소드가 리턴 RouterFunction을 , 우리는 체인이 강력하고 복잡한 라우팅 체계를 구축 할 수 있습니다.

4. 기능성 웹을 사용한 반응 형 REST 애플리케이션

이전 가이드에서는 @RestControllerWebClient를 사용하여 간단한 EmployeeManagement REST 애플리케이션을 만들었습니다 .

이제 라우터와 핸들러 함수를 사용하여 동일한 로직을 구현해 보겠습니다.

먼저 RouterFunction사용하여 경로를 생성하여 Employee 의 반응 스트림을 게시하고 소비해야 합니다.

경로는 Spring Bean으로 등록되며 모든 구성 클래스 내에서 생성 될 수 있습니다.

4.1. 단일 리소스

단일 직원 리소스 를 게시하는 RouterFunction 을 사용하여 첫 번째 경로를 만들어 보겠습니다 .

@Bean RouterFunction getEmployeeByIdRoute() { return route(GET("/employees/{id}"), req -> ok().body( employeeRepository().findEmployeeById(req.pathVariable("id")), Employee.class)); }

첫 번째 인수는 요청 술어입니다. 여기에서 어떻게 정적으로 가져온 RequestPredicates.GET 메서드를 사용했는지 확인 하십시오. 두 번째 매개 변수는 술어가 적용되는 경우 사용될 핸들러 함수를 정의합니다.

즉, 위의 예는 / employees / {id} 에 대한 모든 GET 요청 을 EmployeeRepository # findEmployeeById (String id) 메서드로 라우팅합니다 .

4.2. 수집 자원

다음으로 컬렉션 리소스를 게시하기 위해 다른 경로를 추가하겠습니다.

@Bean RouterFunction getAllEmployeesRoute() { return route(GET("/employees"), req -> ok().body( employeeRepository().findAllEmployees(), Employee.class)); }

4.3. 단일 리소스 업데이트

마지막으로 Employee 리소스 를 업데이트하기위한 경로를 추가해 보겠습니다 .

@Bean RouterFunction updateEmployeeRoute() { return route(POST("/employees/update"), req -> req.body(toMono(Employee.class)) .doOnNext(employeeRepository()::updateEmployee) .then(ok().build())); }

5. 경로 구성

또한 단일 라우터 기능에서 경로를 함께 구성 할 수도 있습니다.

위에서 만든 경로를 결합하는 방법을 살펴 보겠습니다.

@Bean RouterFunction composedRoutes() { return route(GET("/employees"), req -> ok().body( employeeRepository().findAllEmployees(), Employee.class)) .and(route(GET("/employees/{id}"), req -> ok().body( employeeRepository().findEmployeeById(req.pathVariable("id")), Employee.class))) .and(route(POST("/employees/update"), req -> req.body(toMono(Employee.class)) .doOnNext(employeeRepository()::updateEmployee) .then(ok().build()))); }

Here, we've used RouterFunction.and() to combine our routes.

Finally, we've implemented the complete REST API needed for our EmployeeManagement application, using routers and handlers.

To run the application, we can either use separate routes or the single, composed one that we created above.

6. Testing Routes

We can use WebTestClient to test our routes.

To do so, we first need to bind the routes using the bindToRouterFunction method and then build the test client instance.

Let's test our getEmployeeByIdRoute:

@Test public void givenEmployeeId_whenGetEmployeeById_thenCorrectEmployee() { WebTestClient client = WebTestClient .bindToRouterFunction(config.getEmployeeByIdRoute()) .build(); Employee employee = new Employee("1", "Employee 1"); given(employeeRepository.findEmployeeById("1")).willReturn(Mono.just(employee)); client.get() .uri("/employees/1") .exchange() .expectStatus() .isOk() .expectBody(Employee.class) .isEqualTo(employee); }

and similarly getAllEmployeesRoute:

@Test public void whenGetAllEmployees_thenCorrectEmployees() { WebTestClient client = WebTestClient .bindToRouterFunction(config.getAllEmployeesRoute()) .build(); List employees = Arrays.asList( new Employee("1", "Employee 1"), new Employee("2", "Employee 2")); Flux employeeFlux = Flux.fromIterable(employees); given(employeeRepository.findAllEmployees()).willReturn(employeeFlux); client.get() .uri("/employees") .exchange() .expectStatus() .isOk() .expectBodyList(Employee.class) .isEqualTo(employees); }

We can also test our updateEmployeeRoute by asserting that our Employee instance is updated via EmployeeRepository:

@Test public void whenUpdateEmployee_thenEmployeeUpdated() { WebTestClient client = WebTestClient .bindToRouterFunction(config.updateEmployeeRoute()) .build(); Employee employee = new Employee("1", "Employee 1 Updated"); client.post() .uri("/employees/update") .body(Mono.just(employee), Employee.class) .exchange() .expectStatus() .isOk(); verify(employeeRepository).updateEmployee(employee); }

For more details on testing with WebTestClient please refer to our tutorial on working with WebClient and WebTestClient.

7. Summary

In this tutorial, we introduced the new functional web framework in Spring 5 and looked into its two core interfaces – RouterFunction and HandlerFunction. We also learned how to create various routes to handle the request and send the response.

또한 기능적 엔드 포인트 모델을 사용하여 Spring 5 WebFlux 가이드에서 소개 한 EmployeeManagement 애플리케이션 을 다시 만들었습니다.

항상 그렇듯이 전체 소스 코드는 Github에서 찾을 수 있습니다.