In unserem vorherigen Artikel über Mutiny haben wir einige Grundlagen behandelt. Heute gehen wir tiefer. Vielleicht wissen Sie bereits, dass sich Mutiny um zwei Hauptakteure dreht: Uni und Multi. Stellen Sie sich Uni als Ihren Ansprechpartner für einzelne, asynchrone Operationen vor, während Multi der Star der Show ist und Ereignisströme wie ein Profi handhabt.


Uni<String> singleResult = Uni.createFrom().item("Hello, Mutiny!");
Multi<Integer> numberStream = Multi.createFrom().range(1, 10);

Ereigniskomposition: Wenn asynchrone Streams aufeinandertreffen

Kommen wir nun zum spannenden Teil - dem Kombinieren mehrerer asynchroner Streams. Es ist wie ein DJ zu sein, aber anstatt Tracks zu mischen, orchestrieren Sie Datenflüsse.

Die Kombiniertechnik

Stellen Sie sich vor, Sie holen gleichzeitig Benutzerdaten und deren letzte Bestellung ab. So würden Sie das mit Mutiny kombinieren:


Uni<User> userUni = fetchUser(userId);
Uni<Order> orderUni = fetchLatestOrder(userId);

Uni<String> combined = Uni.combine()
    .all().unis(userUni, orderUni)
    .asTuple()
    .map(tuple -> tuple.getItem1().getName() + " ordered " + tuple.getItem2().getProductName());

Cool, oder? Wir haben gerade eine Symphonie asynchroner Operationen geschaffen.

Der Zip-Manöver

Aber warten Sie, es gibt noch mehr! Wenn Sie Elemente aus zwei Streams paaren müssen, kommt zip zur Rettung:


Multi<Integer> numbers = Multi.createFrom().range(1, 5);
Multi<String> letters = Multi.createFrom().items("a", "b", "c", "d", "e");

Multi<String> zipped = numbers.zip(letters, (n, l) -> n + l);
// Ergebnis: 1a, 2b, 3c, 4d, 5e

Fehlerbehandlung: Wenn Async schiefgeht

Seien wir ehrlich, in der Welt der Asynchronität sind Fehler wie ungeladene Gäste auf einer Party. Aber keine Sorge! Mutiny hat einige clevere Tricks zur Fehlerbehandlung parat.

Der Wiederherstellungs- und Wiederholungs-Tanz


Uni<String> flakeyService = callUnreliableService()
    .onFailure().retry().atMost(3)
    .onFailure().recoverWithItem("Backup Plan Activated!");

Dieser Code versucht Ihren unzuverlässigen Dienst dreimal, bevor er auf einen Backup-Plan zurückgreift. Es ist wie ein Sicherheitsnetz... für Ihr Sicherheitsnetz.

Der Fallback-Flip

Manchmal braucht man einfach einen Plan B:


Uni<WeatherReport> weatherReport = getWeatherReport()
    .onFailure().call(this::logError)
    .onFailure().fallback(WeatherReport::getDefaultReport);

Wenn das Abrufen des Wetterberichts fehlschlägt, protokollieren wir den Fehler und geben einen Standardbericht zurück. Denn seien wir ehrlich, "sonnig mit einer Chance auf Ausnahmen" ist keine echte Vorhersage.

Flusskontrolle: Den Datenstrom zähmen

Beim Umgang mit hochfrequenten Datenströmen ist es wichtig, nicht in einer Flut von Ereignissen zu ertrinken. Mutiny bietet mehrere Rettungsboote, um Sie über Wasser zu halten.

Das Drosselventil


Multi<String> throttledStream = sourceStream
    .throttle().iterations(10).per(Duration.ofSeconds(1))
    .onOverflow().drop();

Dieser Code stellt sicher, dass wir nicht mehr als 10 Elemente pro Sekunde verarbeiten und überschüssige Elemente verwerfen. Es ist wie ein Türsteher für Ihren Daten-Nachtclub.

Der Rückdruck-Rückhalt

Wenn Sie mehr Kontrolle über den Rückdruck benötigen:


Multi<Data> controlledStream = sourceStream
    .onOverflow().buffer(100)
    .onItem().transformToUniAndMerge(this::processSlowly);

Hier puffern wir bis zu 100 Elemente und verarbeiten sie einzeln, um sicherzustellen, dass unsere nachgelagerten Operationen nicht überfordert werden. Es ist das asynchrone Äquivalent von "langsam und stetig gewinnt das Rennen".

Multi-Meisterschaft: Streaming wie ein Profi

Die Arbeit mit Multi ist dort, wo Mutiny wirklich glänzt. Es ist wie ein Schweizer Taschenmesser für Datenströme. (Verdammt, ich habe es wieder mit dem Schweizer Taschenmesser gemacht. Sagen wir, es ist wie eine voll ausgestattete Werkstatt für Datenströme.)

Das Filter-Map-Collect-Trifecta


Multi.createFrom().range(1, 100)
    .filter(n -> n % 2 == 0)
    .map(n -> n * n)
    .collect().asList()
    .subscribe().with(
        list -> System.out.println("Quadratische gerade Zahlen: " + list),
        error -> System.err.println("Oops: " + error)
    );

Dieser Code filtert gerade Zahlen, quadriert sie, sammelt die Ergebnisse in einer Liste und druckt sie dann aus. Es ist wie eine Montagelinie für Ihre Daten, aber viel cooler.

Asynchrone Transaktionen: Der Mutiny-Weg

Der Umgang mit Transaktionen in einer asynchronen Welt kann knifflig sein, aber Mutiny macht es einfach.


Uni<TransactionResult> txResult = Uni.createFrom().item(() -> beginTransaction())
    .chain(tx -> performOperation1(tx)
        .chain(() -> performOperation2(tx))
        .chain(() -> commitTransaction(tx))
        .onFailure().call(() -> rollbackTransaction(tx))
    );

Dieser Code startet eine Transaktion, führt zwei Operationen aus und führt je nach Ergebnis entweder einen Commit oder einen Rollback durch. Es ist wie ein Sicherheitsgurt beim Balancieren auf einem Drahtseil.

Synchron und Asynchron verbinden: Das Beste aus beiden Welten

Manchmal müssen Sie synchronen und asynchronen Code mischen. Mutiny hat Sie abgedeckt:


CompletableFuture<String> future = someAsyncOperation();
Uni<String> uni = Uni.createFrom().completionStage(future);

// Oder umgekehrt
Uni<Integer> uni = Uni.createFrom().item(() -> 42);
CompletableFuture<Integer> future = uni.subscribeAsCompletionStage();

Es ist wie zweisprachig in der Programmierwelt zu sein - Sie können mühelos zwischen synchronen und asynchronen Dialekten wechseln.

Paralleles Universum: Parallelität mit Mutiny

Wenn Sie die Dinge beschleunigen müssen, ist Parallelität Ihr Freund:


Multi<String> results = Multi.createFrom().iterable(hugeListOfUrls)
    .onItem().transformToUniAndMerge(url -> 
        Uni.createFrom().item(() -> fetchData(url))
           .runSubscriptionOn(Infrastructure.getDefaultWorkerPool())
    )
    .collect().asList();

Dieser Code ruft Daten von mehreren URLs parallel ab und nutzt den Worker-Pool von Quarkus. Es ist wie ein Team von Ninjas, das Ihre Daten schnell und effizient abruft.

Reaktive REST-APIs: Die Mutiny-Edition

Der Aufbau reaktiver REST-APIs mit Mutiny ist ein Kinderspiel. Hier ist ein kurzes Beispiel:


@Path("/users")
public class UserResource {
    @Inject
    UserService userService;

    @GET
    @Path("/{id}")
    public Uni<Response> getUser(@PathParam("id") Long id) {
        return userService.findById(id)
            .onItem().transform(user -> Response.ok(user).build())
            .onFailure().recoverWithItem(Response.status(Status.NOT_FOUND).build());
    }
}

Dieser Endpunkt gibt einen Benutzer anhand der ID zurück und behandelt den Fall, dass er nicht gefunden wird, elegant. Es ist reaktiv, sauber und bereit für hohe Parallelität.

Zusammenfassung: Mutiny Best Practices

Während wir unsere Reise durch das asynchrone Wunderland von Mutiny abschließen, hier einige abschließende Gedanken:

  • Umarmen Sie das reaktive Paradigma - denken Sie in Strömen und Ereignissen.
  • Verwenden Sie Uni für einzelne asynchrone Operationen und Multi für Streams.
  • Behandeln Sie Fehler proaktiv - die onFailure()-Kette ist Ihr Freund.
  • Kontrollieren Sie den Fluss - verwenden Sie Rückdruckmechanismen, um Ihr System nicht zu überlasten.
  • Kombinieren Sie synchronen und asynchronen Code sorgfältig - Mutiny bietet Werkzeuge, aber verwenden Sie sie mit Bedacht.
  • Testen Sie gründlich - asynchroner Code kann knifflig sein, daher ist umfassendes Testen entscheidend.

Denken Sie daran, mit großer Macht kommt große Verantwortung. Mutiny gibt Ihnen die Werkzeuge, um hochgradig parallele, effiziente Anwendungen zu erstellen, aber es liegt an Ihnen, sie weise einzusetzen.

Gehen Sie nun hinaus und erobern Sie die asynchrone Welt mit Mutiny! Und denken Sie daran, in der Welt der reaktiven Programmierung ist die einzige Konstante der Wandel. Viel Spaß beim Programmieren!