Anwendungsfälle von Kotlin-Coroutines für die Domänenschicht
Dies ist ein übersetztes Kapitel aus dem Buc Kotlin Coroutines. Wenn Sie mir helfen möchten, die Übersetzung zu verbessern, finden Sie die Quellen auf GitHub.
Jetzt besprechen wir die gängigen Anwendungsfälle von Kotlin Coroutines in der Domänenschicht. Hier wird die Geschäftslogik implementiert, daher definieren wir hier Anwendungsfälle, Dienstleistungen, Fassadenklassen, usw. In dieser Schicht sollten wir vermeiden, auf Coroutine-Scope-Objekten zu operieren und aussetzende Funktionen offenzulegen. Die darunterliegende Schicht (die Präsentationsschicht) ist dafür verantwortlich, Coroutinen auf den Scope-Objekten zu starten; in der Domänenschicht sollten wir Funktionen des Coroutine-Scopes verwenden, um Coroutinen zu starten.
In der Praxis haben wir in der Domänenschicht hauptsächlich aussetzende Funktionen, die andere aussetzende Funktionen aufrufen.
Gleichzeitige Aufrufe
Wenn wir möchten, dass zwei Prozesse parallel ablaufen, sollten wir unseren Funktionskörper mit coroutineScope
umschließen und den async
Builder darin verwenden, um jeden Prozess zu starten, der asynchron laufen soll.
Es ist unerlässlich zu verstehen, dass diese Änderung nur diese beiden Prozesse parallel laufen lassen sollte. Alle anderen Mechanismen, wie Stornierung, Ausnahmebehandlung oder Kontextweitergabe, sollten gleich bleiben. Wenn Sie also die Funktionen produceCurrentUserSeq
und produceCurrentUserPar
unten betrachten, liegt der einzige wichtige Unterschied darin, dass die erste sequentiell ist, während die zweite zwei parallele Prozesse startet8.
Wenn wir zwei asynchrone Prozesse starten und dann auf deren Vollendung warten möchten, können wir das tun, indem wir für jeden von ihnen eine neue Coroutine mittels der async
Funktion erstellen. Allerdings kann das gleiche Ergebnis auch erreicht werden, wenn wir nur einen Prozess mittels async
starten und den zweiten im gleichen Coroutine laufen lassen. Die folgende Implementierung von produceCurrentUserPar
wird praktisch das gleiche Verhalten wie die vorherige aufweisen. Welche Option sollte bevorzugt werden? Ich denke, die meisten Entwickler werden die erste Option bevorzugen, weil die Nutzung von async
für jeden Prozess, den wir parallel starten möchten, unseren Code verständlicher gestaltet. Andererseits würden einige Entwickler die zweite Option vorziehen, da sie effizienter ist, indem sie weniger Coroutines nutzt und weniger Objekte erzeugt. Es ist Ihre Entscheidung, welche Option Sie bevorzugen.
Wir können async
zusammen mit Sammlungsfunktionen verwenden, um für jedes Listenelement einen asynchronen Prozess zu initiieren. In solchen Fällen ist es beste Praxis, die Ergebnisse mit der Funktion awaitAll
zu erwarten.
Wenn Sie die Anzahl der gleichzeitigen Aufrufe begrenzen möchten, können Sie einen Rate Limiter verwenden. Beispielsweise bietet die Resilience4j-Bibliothek Rate Limiter für suspendierten Funktionen an. Sie können Ihre Liste auch in Flow
umwandeln und dann flatMapMerge
mit dem concurrency
Parameter verwenden, der angibt, wie viele gleichzeitige Aufrufe Sie senden werden10.
Wenn Sie coroutineScope
verwenden, denken Sie daran, dass eine Ausnahme in irgendeiner untergeordneten Coroutine die durch coroutineScope
erstellte Coroutine abbricht, alle anderen Unterprozesse abbricht und dann eine Ausnahme auslöst. Dies ist das Verhalten, das wir normalerweise erwarten, aber in einigen Fällen ist es für uns nicht sehr passend. Wenn wir eine Reihe von gleichzeitigen Prozessen starten möchten, die wir als unabhängig betrachten, sollten wir stattdessen supervisorScope
verwenden, welches Ausnahmen in seinen Unterprozessen ignoriert11.
Wenn wir die Ausführungszeit eines Prozesses begrenzen möchten, können wir withTimeout
oder withTimeoutOrNull
verwenden, die beide ihren Prozess abbrechen, wenn er länger dauert als die durch das Argument angegebene Zeit12.
Flow-Transformationen
Bevor wir diesen Abschnitt abschließen, sollten wir auch die typischen Wege besprechen, wie wir Flow verarbeiten. In den meisten Fällen verwenden wir einfach grundlegende Flow-Verarbeitungsfunktionen wie map
, filter
oder onEach
, und gelegentlich weniger gebräuchliche Funktionen wie scan
oder flatMapMerge
13.
Wenn Sie zwei Ströme zusammenführen möchten, könnten Sie Funktionen wie merge
, zip
oder combine
14 verwenden.
Wenn Sie möchten, dass ein einzelner Flow von mehreren Coroutinen überwacht wird, transformieren Sie ihn in SharedFlow
. Eine gängige Methode, um dies zu tun, besteht darin, shareIn
mit einem Scope zu verwenden. Um diesen Flow nur dann aktiv zu halten, wenn es Abonnenten gibt, verwenden Sie die Option WhileSubscribed
für den started
Parameter15.
Siehe das Kapitel Coroutine Scope Functions für weitere Details.
Siehe den Abschnitt flatMapConcat
, flatMapMerge
, flatMapLatest
im Kapitel Flow processing für weitere Informationen.
Weitere Einzelheiten finden Sie im Abschnitt supervisorScope des Kapitels Coroutine Scope Functions.
Weitere Informationen finden Sie im Abschnitt withTimeout des Kapitels Coroutine Scope Functions.
Für weitere Details siehe das Kapitel Flow processing.
Siehe den Abschnitt merge
, zip
, combine
im Kapitel Flow processing für weitere Einzelheiten.
Weitere Informationen finden Sie im Kapitel SharedFlow and StateFlow.