Reaktive - Multi-Plattform-Bibliothek für reaktive Kotlin



Viele lieben heute reaktives Programmieren. Es hat viele Vorteile: das Fehlen der sogenannten " Rückruf-Hölle " und des eingebauten Fehlerbehandlungsmechanismus sowie eines funktionalen Programmierstils, der die Wahrscheinlichkeit von Fehlern verringert. Deutlich einfacher zu schreibender Multithread-Code und einfacher zu verwaltende Datenströme (kombinieren, teilen und konvertieren).

Viele Programmiersprachen haben ihre eigene reaktive Bibliothek: RxJava für JVM, RxJS für JavaScript, RxSwift für iOS, Rx.NET usw.

Aber was haben wir für Kotlin? Es wäre logisch anzunehmen, dass RxKotlin. Eine solche Bibliothek existiert zwar, aber es handelt sich nur um eine Reihe von Erweiterungen für RxJava2, den sogenannten "Zucker".

Und im Idealfall hätte ich gerne eine Lösung, die die folgenden Kriterien erfüllt:

  • Multi-Plattform - um Multi-Plattform-Bibliotheken mithilfe reaktiver Programmierung schreiben und innerhalb des Unternehmens verteilen zu können;
  • Nullsicherheit - Das System vom Typ Kotlin schützt uns vor „ Milliarden-Dollar-Fehlern “, daher müssen Nullwerte gültig sein (z. B. Observable<String?> ).
  • Kovarianz und Kontravarianz sind ein weiteres sehr nützliches Merkmal von Kotlin, mit dem beispielsweise der Typ Observable<String> sicher in Observable<CharSequence> .

Wir bei Badoo haben beschlossen, nicht auf das Wetter am Meer zu warten und eine solche Bibliothek erstellt. Wie Sie vielleicht erraten haben, haben wir es Reaktive genannt und auf GitHub gepostet.

In diesem Artikel werden wir uns die reaktiven Programmiererwartungen von Kotlin genauer ansehen und sehen, wie die Funktionen von Reaktive mit diesen übereinstimmen.

Drei natürliche reaktive Vorteile


Multi-Plattform


Der erste natürliche Vorteil ist am wichtigsten. Unsere Web-Teams für iOS, Android und Mobile existieren derzeit separat. Die Anforderungen sind allgemein, das Design ist das gleiche, aber jedes Team macht seine eigene Arbeit.

Mit Kotlin können Sie plattformübergreifenden Code schreiben, aber Sie müssen die reaktive Programmierung vergessen. Und ich möchte in der Lage sein, gemeinsam genutzte Bibliotheken mithilfe reaktiver Programmierung zu schreiben und innerhalb des Unternehmens zu verteilen oder auf GitHub hochzuladen. Möglicherweise kann dieser Ansatz die Entwicklungszeit erheblich verkürzen und die Gesamtmenge an Code reduzieren.

Null Sicherheit


Es geht eher um den Fehler von Java und RxJava2. Kurz gesagt, null kann nicht verwendet werden. Versuchen wir herauszufinden, warum. Schauen Sie sich diese Java-Oberfläche an:

 public interface UserDataSource {   Single<User> load(); } 

Kann das Ergebnis null sein? Um Mehrdeutigkeiten zu vermeiden, ist null in RxJava2 nicht zulässig. Und wenn Sie es noch brauchen, ist das Vielleicht und Optional. Aber in Kotlin gibt es keine derartigen Probleme. Wir können sagen, dass Single<User> und Single<User?> Unterschiedliche Typen sind und alle Probleme bei der Kompilierung auftreten.

Kovarianz und Kontravarianz


Dies ist eine Besonderheit von Kotlin, die in Java sehr fehlt. Weitere Informationen hierzu finden Sie im Handbuch . Ich werde nur einige interessante Beispiele dafür geben, welche Probleme bei der Verwendung von RxJava in Kotlin auftreten.

Kovarianz :

 fun bar(source: Observable<CharSequence>) { } fun foo(source: Observable<String>) {   bar(source) //   } 

Da Observable eine Java-Schnittstelle ist, wird dieser Code nicht kompiliert. Dies liegt daran, dass generische Typen in Java unveränderlich sind. Sie können natürlich out verwenden, aber die Verwendung von Operatoren wie scan führt erneut zu einem Kompilierungsfehler:

 fun bar(source: Observable<out CharSequence>) {   source.scan { a, b -> "$a,$b" } //   } fun foo(source: Observable<String>) {   bar(source) } 

Die Scan-Anweisung unterscheidet sich darin, dass ihr generischer Typ "T" sowohl Eingabe als auch Ausgabe ist. Wenn Observable die Kotlin-Schnittstelle wäre, könnte ihr Typ T als out bezeichnet werden, und dies würde das Problem lösen:

 interface Observable<out T> {   … } 

Und hier ist ein Beispiel mit Kontravarianz:

 fun bar(consumer: Consumer<String>) { } fun foo(consumer: Consumer<CharSequence>) {   bar(consumer) //   } 

Aus dem gleichen Grund wie im vorherigen Beispiel (generische Typen in Java sind unveränderlich) wird dieses Beispiel nicht kompiliert. Durch Hinzufügen wird das Problem gelöst, aber auch hier nicht hundertprozentig:

 fun bar(consumer: Consumer<in String>) {   if (consumer is Subject) {       val value: String = consumer.value //     } } fun foo(consumer: Consumer<CharSequence>) {   bar(consumer) } interface Subject<T> : Consumer<T> {   val value: T } 

Nun, traditionell wird dieses Problem in Kotlin durch die Verwendung von in in der Schnittstelle gelöst:

 interface Consumer<in T> {   fun accept(value: T) } 

Variabilität und Kontravarianz generischer Typen sind daher der dritte natürliche Vorteil der Reaktive Bibliothek.

Kotlin + Reaktiv = Reaktiv


Wir kommen zur Hauptsache - der Beschreibung der Reaktiven Bibliothek.

Hier sind einige seiner Funktionen:

  1. Es ist plattformübergreifend, was bedeutet, dass Sie endlich allgemeinen Code schreiben können. Bei Badoo betrachten wir dies als einen der wichtigsten Vorteile.
  2. Es ist in Kotlin geschrieben, was uns die oben beschriebenen Vorteile bietet: Es gibt keine Einschränkungen für Null, Varianz / Kontravarianz. Dies erhöht die Flexibilität und bietet Sicherheit beim Kompilieren.
  3. Es besteht keine Abhängigkeit von anderen Bibliotheken wie RxJava, RxSwift usw., sodass die Bibliotheksfunktionalität nicht auf einen gemeinsamen Nenner gebracht werden muss.
  4. Reine API. Beispielsweise wird die ObservableSource Schnittstelle in Reaktive einfach als Observable , und alle Operatoren sind Erweiterungsfunktionen, die sich in separaten Dateien befinden. Es gibt keine Gottklassen mit 15.000 Zeilen. Dies ermöglicht eine einfache Erweiterung der Funktionalität, ohne Änderungen an vorhandenen Schnittstellen und Klassen vorzunehmen.
  5. Unterstützung für Scheduler (unter Verwendung der bekannten observeOn subscribeOn und observeOn ).
  6. Kompatibel mit RxJava2 (Interoperabilität), bietet Quellkonvertierung zwischen Reaktive und RxJava2 und die Möglichkeit, Scheduler von RxJava2 wiederzuverwenden.
  7. ReactiveX- Konformität.

Ich möchte etwas mehr über die Vorteile sprechen, die wir aufgrund der Tatsache erhalten haben, dass die Bibliothek in Kotlin geschrieben ist.

  1. In Reaktive sind Nullwerte zulässig, da dies in Kotlin sicher ist. Hier einige interessante Beispiele:
    • observableOf<String>(null) //
    • val o1: Observable<String?> = observableOf(null)
      val o2: Observable<String> = o1 // ,
    • val o1: Observable<String?> = observableOf(null)
      val o2: Observable<String> = o1.notNull() // , null
    • val o1: Observable<String> = observableOf("Hello")
      val o2: Observable<String?> = o1 //
    • val o1: Observable<String?> = observableOf(null)
      val o2: Observable<String> = observableOf("Hello")
      val o3: Observable<String?> = merge(o1, o2) //
      val o4: Observable<String> = merge(o1, o2) // ,

    Variation ist auch ein großer Vorteil. In der Observable Schnittstelle wird beispielsweise Typ T als out deklariert, wodurch Folgendes geschrieben werden kann:

     fun foo() {   val source: Observable<String> = observableOf("Hello")   bar(source) //   } fun bar(source: Observable<CharSequence>) { } 

So sieht die Bibliothek heute aus:

  • Status zum Zeitpunkt des Schreibens: Alpha (einige Änderungen in der öffentlichen API sind möglich);
  • unterstützte Plattformen: JVM und Android;
  • unterstützte Quellen: Observable , Maybe , Single und Completable ;
  • Es wird eine relativ große Anzahl von Operatoren unterstützt, darunter Map, Filter, FlatMap, ConcatMap, CombineLatest, Zip, Merge und andere (die vollständige Liste finden Sie auf GitHub ).
  • Die folgenden Scheduler werden unterstützt: Berechnung, E / A, Trampolin und Haupt;
  • Themen: PublishSubject und BehaviorSubject;
  • Gegendruck wird noch nicht unterstützt, aber wir denken über die Notwendigkeit und Implementierung dieser Funktion nach.

Was sind unsere Pläne für die nahe Zukunft:

  • Verwenden Sie Reaktive in unseren Produkten (wir erwägen derzeit Optionen).
  • JavaScript-Unterstützung (Pull-Anfrage wird bereits geprüft);
  • iOS-Unterstützung
  • Veröffentlichen von Artefakten in JCenter (derzeit unter Verwendung des JitPack-Dienstes);
  • Dokumentation
  • Erhöhung der Anzahl der unterstützten Betreiber;
  • Tests
  • Weitere Plattformen - Pull-Anfragen sind willkommen!

Sie können die Bibliothek jetzt ausprobieren und auf GitHub alles finden, was Sie brauchen. Teilen Sie Ihre Erfahrungen und stellen Sie Fragen. Für jedes Feedback sind wir dankbar.

Source: https://habr.com/ru/post/de447424/


All Articles