Schrittweise Migration von Spring MVC auf Spring Webflux (Teil 2)
19 Mar 2021Wie im letzten Post beschrieben, hatten wir einen Plan für die kommenden Änderungen bei der kompletten Umstellung auf Webflux. Der Plan ist aufgegangen, jedoch kam es zu einigen unerwarteten Aufwänden. Die beiden Wesentlichen möchte ich kurz zusammenfassen:
Unerwartete Aufgaben: Ersatz der ThreadLocals durch Context
Nachdem wir die spring-boot-starter-web
Abhängigkeiten komplett entfernt hatten, mussten wir feststellen, dass der vorherige Workaround für das Übergeben der ThreadLocals via Schedulers.onScheduleHook
nicht mehr zuverlässig funktionierte.
Von daher mussten wir (wie in einem späteren Schritt geplant) alle vorhandenen ThreadLocals ersetzen und explizit via Mono.contextWrite
setzen und über ContextView.getOrDefault
auslesen:
Schreiben in den Contexts im WebFilter:
webFilterChain.filter(serverWebExchange)
.contextWrite(context -> context.put(SomeClass.class, someObject));
Lesen aus dem Context:
Mono.deferContextual(context -> Mono.just(context.getOrDefault(SomeClass.class, defaultValue)));
Unerwartete Aufgaben: Ersatz von SpringFox
Des Weiteren war die aktuell eingesetzte Library (springfox 2.9.2) für die Generierung der Swagger-Dokumentation nicht mehr kompatibel. Aus mehreren Gründen hat uns springfox 3.0.0 nicht zugesagt, von daher haben wir es durch springdoc-openapi ersetzt, und alle Swagger 1.1 Annotationen durch Swagger 2.1 ersetzt.
Positiv überraschendes Verhalten
Wenn ein Aufrufer einen Request an den Service abbricht, bricht Spring Webflux alle in diesem Request ausgelösten Subrequests kaskadierent ab. In Spring MVC ist dieses Verhalten nicht möglich, da diese Requests komplett entkoppelt sind. Es macht Sinn, diesen Abbruch explizit zu loggen.
webClient.post()
.uri(url.toString())
.. // weitere Konfigurationen des Webclients
.doOnCancel(() -> {
// log that parent request was cancelled
})
Fazit
Die Umstellung auf Webflux kostet insbesondere dann Zeit, wenn viel mit ThreadLocals gearbeitet wurde und ältere Libraries eingesetzt werden, die nicht kompatibel mit dem Framework sind. Zudem darf auch der Aufwand für den Wissensaufbau und -transfer im Team nicht unterschätzen. Nichtsdestotrotz hat sich der Umstieg aus Performance und Wartungssicht für diesen Anwendungsfall gelohnt. Wo früher eine Parallelisierung von Anfragen mühsam und fehleranfällig orchestriert werden musste, wird dies durch die reaktive Programmierweise vom Framework abgenommen.