一般而言,WebFlux是有別於Servlet容器為基礎的Web技術堆疊,採用Reactive為典範,適合REST風格的Web應用程式。那麼,傳統上基於同步、非REST風格的Web應用程式呢?有機會採用WebFlux嗎?

在WebFlux上的MVC?

關於Spring 5中正式釋出的WebFlux,我曾經在〈取代Web MVC的Flux?〉中談到,其技術堆疊中隱含的,是非同步、非阻斷、Reactive、函數式的心智模型。若應用場景中是新專案,一開始就朝著這方向前進,要按照WebFlux規範的Reactive API來實作應用程式時,應該會比較順利一些。

當然,不是每個專案都能符合如此全好的假設。Spring團隊也知道,一個全新技術堆疊要能被接受,總是要提供和緩一些的導入曲線,才能被開發者接受,因而WebFlux中可基於標註方式來開發(另一方式是全面的函數式API),而且大量採用了Spring MVC中既有的標註,以便熟悉Spring MVC的開發者易於理解與上手,因此,要將一個既存的Spring MVC專案,透過適當修改而執行在WebFlux,是有可能的,這也有利於評估日後是否全面採用。

將Spring MVC專案轉移至WebFlux前,首先要重構,盡量運用Spring MVC的API來取代Servlet API,雖然在某些情境下,仍能運用Servlet API(執行在Tomcat並透過TomcatReactiveWebServerFactory),然而並不建議,這只會令開發者疲於應付API之間的轉換,如果專案本身已經適當運用了Spring MVC,轉換時,才能專注在Reactive相關的API銜接。

在轉換至WebFlux環境之後,大部份Spring MVC的標註雖然可以運作,然而行為上會有些差異,例如@RequestParam, WebFlux中只能用來擷取URL上附加的請求參數,而POST的請求參數不附在URL,也就無法運用@RequestParam,解決的方式之一,是自訂表單物件並注入處理器,另一個方式是透過ServerWebExchange,它代表了當次請求回應。

例如,表單的資訊可以透過ServerWebExchange的getFormData來取得,而所傳回的型態是Mono<MultiValueMap<String, String>>,若控制器還是同步風格的程式碼居多,暫時不想大改程式的話,可直接呼叫它的block方法,以同步風格來取得想要的資料,讓應用程式先能運作,不過,採取阻斷式風格,無法發揮WebFlux高效處理的益處,block方法只是先求能夠運作而已。

若使用Thymeleaf模版,也會因POST的請求參數不附在URL,而出現WebFlux就不理會${param.xxx}的問題,此時,我們可以在處理器中將請求參數取出,設定為請求範圍屬性,來解決這個問題。

Reactor與阻斷式API的銜接

既有專案若能在WebFlux運作,接著就是將專案中阻斷式的流程,運用Reactor的API令其成為Reactive資料流、非阻斷風格的流程,Reactor本身的Flux或Mono有不少方法,銜接陣列、Iterable、Optional、Stream、CompletableFuture等API,在比較複雜的情況下,也有defer、generate或create可運用。

在與資料庫相關的API銜接上,值得特別討論。若想基於JDBC來處理資料庫,有個簡單的想法是,透過Flux、Mono的方法,像是defer方法來轉接,並使用subscribeOn令資料流在訂閱時,會是在另一執行緒上,但問題是該在哪一層轉接?Service還是DAO層次?這就看怎麼處理比較方便,或者更有效益。

在Service層次轉接或許比較簡單,因為JDBC本身是同步式API,若想在DAO層次修改,令相關方法傳回Flux、Mono,相對麻煩許多,因為等於要封裝JDBC的相關API細節。於是,有些程式庫做了這類的動作,例如rxjava2-jdbc,有興趣瞭解的話,可看看〈Spring WebFlux and rxjava2-jdbc〉這篇的介紹。

以rxjava2-jdbc來說,實現時,雖然用了非阻斷式連線池,以及一些資料流封裝,然而底層還是使用同步的JDBC;有些資料庫提供非同步的驅動程式,例如MongoDB,運用這類非同步驅動程式,在銜接至Reactor或是WebFlux時,才能有實質益處。

當然,像MongoDB提供的這類非同步驅動程式,並不是JDBC標準規範,在Java商標擁有者Oracle這方,正在推行Asynchronous Database Access API,簡稱ADBA,希望成為未來非同步的資料庫驅動程式,目前來說,可用性還未知,就看到的示範程式碼來看,是基於CompletableFuture之類的API來實現。

只是Spring並不怎麼認同ADBA,他們希望基於Reactive風格,而非單純的非同步,為此,Spring在2018的SpringOne 大會提出R2DBC,其API基礎是自家Reactor專案,在〈Reactive Programming and Relational Databases〉,可略為看到ADBA與R2DBC的API比較,目前R2DBC還是未正式釋出的階段。

在SpringOne大會上,Ben Hale也談到,R2DBC的目的之一是要影響ADBA規範,還滿嗆地說:「如果他們搞不清楚Reactive與非同步的差別,那就由Spring團隊來做!」

當然,如果能基於Spring Data的設計模型來封裝底層,讓API運用上更一致就好了。

對於本身已經提供非同步驅動程式的資料庫,Spring Data提供Reactive的Repository版本,例如,MongoDB就有ReactiveMongoRepository,它實現了ReactiveCrudRepository,相關的方法都是傳回Flux或Mono;而在R2DBC這部份,也有Spring Data R2DBC,目前同樣是未正式釋出的階段。

Web層的重構

在其他阻斷式API都被銜接至Reactive風格的資料流之後,接著,就可以來處理WebFlux的控制器了。@Controller標註的控制器處理器方法可以傳回字串,Model中也可放入Mono、Flux或字串,問題就來了,該傳回Mono相關物件還是字串?這關乎兩個考量,一是API銜接的方便性,二是儘早執行完處理器方法,讓流程控制權儘早回到WebFlux,進而讓Netty早點釋放分配的執行緒,以服務下一個請求。

如果處理器呼叫的服務層等元件,本身就是傳回Flux或者是Mono,可透過filter、map、flatMap等操作,將資料流做進一步處理後,傳回Flux或Mono,此時,控制器可直接傳回這些物件,或者直接在Model中放入這些物件;而對於會阻斷的操作,或者是CPU密集性的計算過久,可以考慮將整個處理器中的流程,封裝至Mono或Flux。

若既有專案用Spring Security,在WebFlux對應的版本是Security Reactive,基本上,在設定模型類似,但API有些不同,例如Security Reactive頁面防護的部份,是透過ServerHttpSecurity,驗證來源的部份,也有自己的ReactiveUserDetailsService、ReactiveAuthenticationManager,而過濾器處理的部份,則是透過Spring的org.springframework.web.server.WebFilter。

當然,實際轉換時,還可能遇上更多問題,有興趣的話,我們可以看看〈趣改gossip〉(https://goo.gl/TQtZmU),其中提及將Spring MVC的應用程式,重構到採用WebFlux堆疊的實際流程。

全面Reactive?

那麼,該全面採用Reactive嗎?全面採行的話,維護性是個爭議點,例如,就程式的可讀性等方面來說,懂Reactive與熟練函數式風格的開發者,可能覺得寫來很爽,然而,也有其他開發者持反對的態度,認為Reactive或函數式風格用得過火的話,整個應用程式反而難以理解;另一個問題在於相關技術生態系的問題,如果WebFlux真有其效率上的優點,應用程式其他部件也要能配合,才能發揮。

不過,就熟悉Reactive風格開發來說,Reactor與WebFlux是個不錯的對象,如果過去開發者在接觸Reactive風格開發上,曾遭遇挫折,如果試著以重構的方式,讓既有應用程式搭配Rector與WebFlux來運作,或許會有不一樣的體驗。

專欄作者

熱門新聞

Advertisement