Heiko Maaß Software-Entwicklung und Elektronische Musik

Schrittweise Migration von Spring MVC auf Spring Webflux (Teil 2)

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