Pengantar Kerangka Web Fungsional di Musim Semi 5

1. Perkenalan

Spring WebFlux adalah kerangka kerja web fungsional baru yang dibuat menggunakan prinsip reaktif.

Dalam tutorial ini, kita akan belajar bagaimana menggunakannya dalam praktik.

Kami akan mendasarkan ini dari panduan kami yang ada untuk Spring 5 WebFlux. Dalam panduan itu, kami membuat aplikasi REST reaktif sederhana menggunakan komponen berbasis anotasi. Di sini, kami akan menggunakan kerangka fungsional sebagai gantinya.

2. Ketergantungan Maven

Kita membutuhkan dependensi spring-boot-starter-webflux yang sama seperti yang didefinisikan di artikel sebelumnya:

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

3. Kerangka Web Fungsional

Kerangka web fungsional memperkenalkan model pemrograman baru tempat kami menggunakan fungsi untuk merutekan dan menangani permintaan.

Berbeda dengan model berbasis anotasi di mana kita menggunakan pemetaan anotasi, di sini kita akan menggunakan HandlerFunction dan RouterFunction .

Demikian pula, seperti pada pengontrol beranotasi, pendekatan titik akhir fungsional dibangun di atas tumpukan reaktif yang sama.

3.1. HandlerFunction

The HandlerFunction merupakan fungsi yang menghasilkan respon untuk permintaan dialihkan kepada mereka:

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

Antarmuka ini pada dasarnya adalah Fungsi , yang berperilaku sangat mirip servlet.

Meskipun, dibandingkan dengan layanan # Servlet standar (ServletRequest req, ServletResponse res) , HandlerFunction tidak mengambil respons sebagai parameter input.

3.2. Fungsi Router

RouterFunction berfungsi sebagai alternatif untuk anotasi @RequestMapping . Kita dapat menggunakannya untuk mengarahkan permintaan ke fungsi handler:

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

Biasanya, kita dapat mengimpor fungsi helper RouterFunctions.route () untuk membuat rute, daripada menulis fungsi router lengkap.

Ini memungkinkan kita untuk merutekan permintaan dengan menerapkan RequestPredicate. Ketika predikatnya cocok, maka argumen kedua, fungsi handler, dikembalikan:

public static  RouterFunction route( RequestPredicate predicate, HandlerFunction handlerFunction)

Karena metode route () mengembalikan sebuah RouterFungsi , kita dapat menghubungkannya untuk membangun skema perutean yang kuat dan kompleks.

4. Aplikasi REST Reaktif Menggunakan Web Fungsional

Dalam panduan kami sebelumnya, kami membuat aplikasi REST EmployeeManagement sederhana menggunakan @RestController dan WebClient.

Sekarang, mari mengimplementasikan logika yang sama menggunakan fungsi router dan handler.

Pertama, kita perlu membuat rute menggunakan RouterFunction untuk mempublikasikan dan mengkonsumsi aliran reaktif kami karyawan s .

Rute terdaftar sebagai kacang musim semi dan dapat dibuat di dalam kelas konfigurasi apa pun.

4.1. Sumber Daya Tunggal

Mari buat rute pertama kita menggunakan RouterFungsi yang menerbitkan satu sumber daya Karyawan :

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

Argumen pertama adalah predikat permintaan. Perhatikan bagaimana kami menggunakan metode RequestPredicates.GET yang diimpor secara statis di sini. Parameter kedua mendefinisikan fungsi handler yang akan digunakan jika predikatnya berlaku.

Dengan kata lain, contoh di atas merutekan semua permintaan GET untuk / karyawan / {id} ke metode EmployeeRepository # findEmployeeById (String id) .

4.2. Sumber Daya Koleksi

Selanjutnya, untuk menerbitkan sumber daya koleksi, mari tambahkan rute lain:

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

4.3. Pembaruan Sumber Daya Tunggal

Terakhir, mari tambahkan rute untuk memperbarui sumber daya Karyawan :

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

5. Menyusun Rute

Kami juga dapat menyusun rute bersama dalam satu fungsi router.

Mari kita lihat cara menggabungkan rute yang dibuat di atas:

@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.

Selain itu, kami membuat ulang aplikasi EmployeeManagement kami yang diperkenalkan dalam panduan WebFlux Spring 5 dengan model titik akhir fungsional.

Seperti biasa, kode sumber lengkap dapat ditemukan di Github.