Reactor Mono.fromFuture();

0

Czym różnią się metody:

Mono.fromFuture(() -> completableFuture);

i

Mono.fromFuture(completableFuture);

Jedna z nich przyjmuje supplier'a, druga po prostu bierze wartość. Dlaczego są dwie sygnatury? Jak to się przekłada na efekt w praktyce?

4

Myślę, że można to pokazać na poniższym przykładzie:

    public static void main(String[] args) {

        var innerMono = Mono.fromFuture(callExternalService())
            .doOnSubscribe(it -> System.out.println("[mono] subscribed"))
            .doFinally(signal -> System.out.println("[mono] signal: " + signal));

        innerMono.retry(1)
            .doOnNext(it -> System.out.println("[subscriber] value: " + it))
            .subscribe();
    }

    private static int counter = 0;

    private static CompletableFuture<String> callExternalService() {
        if (counter++ == 0) {
            // simulate failure on the first attempt
            return CompletableFuture.failedFuture(new IOException("Network error"));
        }
        return CompletableFuture.completedFuture("success");
    }

Ten kod symuluje sytuację, gdzie komunikujemy się z jakimś zewnętrznym serwisem i otrzymujemy w rezultacie CompletableFuture. Przykład jest zrobiony tak, że pierwszy call zawsze zwróci future z błędem i dopiero druga próba się powiedzie.

Następnie mamy nasze Mono, które jest skonstruowane w sposób eager - dostaje gotowe już future, które zainicjowało request do zewnętrznego serwisu. Nasz subskrybent podpina się do Mono i chce, żeby był robiony retry, który ma przeciwdziałać błędowi. Kiedy uruchomimy ten kod, otrzymujemy:

[mono] subscribed
[mono] signal: onError
[mono] subscribed
[mono] signal: onError

Jak widać, mimo że nasz subskrybent próbował robić retry (przez ponowną subskrybcję do Mono), to i tak dostał starą wartość. Dzieje się tak dlatego, że Mono które subskrybuje zostało stworzone z gotowego future, którego lifecycle już się zaczął i który skończy się albo sukcesem albo błędem. W przypadku gdy subskrypcja do Mono następuje po tym jak future już się zakończyło, nie pozostaje nic innego jak tylko zwrócić to co zostało już "obliczone" na wyjściu future (przychodzi mi do głowy analogia do kota Schrödingera). Czyli de facto request do zewnętrznego systemu dzieje się tylko raz mimo wielu subskrypcji.

Gdy lekko zmodyfikujemy kod tworzący Mono tak, żeby wykorzystywał drugą metodę (Mono.fromFuture(() -> callExternalService())) dostajemy to co chcemy:

[mono] subscribed
[mono] signal: onError
[mono] subscribed
[subscriber] value: success
[mono] signal: onComplete

Teraz, za każdym razem gdy Mono jest subskrybowane, tworzone jest nowe future (można to uogólnić to rozpoczęcia asynchronicznej operacji) i wszystko śmiga.

TLDR: jedno jest eager a drugie lazy :)

1 użytkowników online, w tym zalogowanych: 0, gości: 1