Reaktive - bibliothèque multi-plateforme pour Kotlin réactif



Beaucoup aiment aujourd'hui la programmation réactive. Il présente de nombreux avantages: l'absence de ce qu'on appelle "l' enfer de rappel ", et le mécanisme de gestion des erreurs intégré, et un style de programmation fonctionnel qui réduit la probabilité de bogues. Significativement plus facile à écrire du code multi-thread et plus facile à gérer les flux de données (combiner, diviser et convertir).

De nombreux langages de programmation ont leur propre bibliothèque réactive: RxJava pour JVM, RxJS pour JavaScript, RxSwift pour iOS, Rx.NET, etc.

Mais qu'avons-nous pour Kotlin? Il serait logique de supposer que RxKotlin. Et, en effet, une telle bibliothèque existe, mais ce n'est qu'un ensemble d'extensions pour RxJava2, le soi-disant «sucre».

Et idéalement, j'aimerais avoir une solution qui réponde aux critères suivants:

  • multi-plateforme - pour pouvoir écrire des bibliothèques multi-plateforme en utilisant une programmation réactive et les distribuer au sein de l'entreprise;
  • Sécurité nulle - le système de type Kotlin nous protège des « erreurs d'un milliard de dollars », donc les valeurs nulles doivent être valides (par exemple, Observable<String?> );
  • la covariance et la contravariance sont une autre caractéristique très utile de Kotlin, qui permet, par exemple, de convertir en toute sécurité le type Observable<String> en Observable<CharSequence> .

À Badoo, nous avons décidé de ne pas attendre le temps au bord de la mer et avons créé une telle bibliothèque. Comme vous l'avez peut-être deviné, nous l'avons appelé Reaktive et l'avons publié sur GitHub .

Dans cet article, nous allons examiner de plus près les attentes de programmation réactive de Kotlin et voir comment les capacités de Reaktive leur correspondent.

Trois avantages naturels naturels


Multiplateforme


Le premier avantage naturel est le plus important. Nos équipes iOS, Android et Web mobile existent actuellement séparément. Les exigences sont générales, la conception est la même, mais chaque équipe fait son propre travail.

Kotlin vous permet d'écrire du code multi-plateforme, mais vous devez oublier la programmation réactive. Et j'aimerais pouvoir écrire des bibliothèques partagées en utilisant une programmation réactive et les distribuer au sein de l'entreprise ou les télécharger sur GitHub. Potentiellement, cette approche peut réduire considérablement le temps de développement et réduire la quantité totale de code.

Sécurité nulle


Il s'agit plutôt de la faille de Java et de RxJava2. En bref, null ne peut pas être utilisé. Essayons de comprendre pourquoi. Jetez un œil à cette interface Java:

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

Le résultat peut-il être nul? Pour éviter toute ambiguïté, null n'est pas autorisé dans RxJava2. Et si vous en avez encore besoin, c'est peut-être et facultatif. Mais à Kotlin, il n'y a pas de tels problèmes. Nous pouvons dire que Single<User> et Single<User?> types différents, et tous les problèmes apparaissent au stade de la compilation.

Covariance et contravariance


C'est une caractéristique distinctive de Kotlin, quelque chose qui manque beaucoup à Java. Vous pouvez en savoir plus à ce sujet dans le manuel . Je ne donnerai que quelques exemples intéressants des problèmes qui surviennent lors de l'utilisation de RxJava dans Kotlin.

Covariance :

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

Comme Observable est une interface Java, ce code ne se compile pas. En effet, les types génériques en Java sont invariants. Vous pouvez, bien sûr, utiliser, mais l'utilisation d'opérateurs comme scan entraînera à nouveau une erreur de compilation:

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

L'instruction scan est différente en ce que son type générique «T» est à la fois entrée et sortie. Si Observable était l'interface de Kotlin, alors son type T pourrait être noté comme out et cela résoudrait le problème:

 interface Observable<out T> {   … } 

Et voici un exemple avec contravariance:

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

Pour la même raison que dans l'exemple précédent (les types génériques en Java sont invariants), cet exemple ne se compile pas. L'ajout résoudra le problème, mais encore une fois pas à cent pour cent:

 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 } 

Eh bien, par tradition, dans Kotlin, ce problème est résolu en utilisant dans l'interface:

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

Ainsi, la variabilité et la contravariance des types génériques sont le troisième avantage naturel de la bibliothèque Reaktive.

Kotlin + Reactive = Reaktive


Nous passons à l'essentiel - la description de la bibliothèque Reaktive.

Voici quelques-unes de ses fonctionnalités:

  1. Il est multi-plateforme, ce qui signifie que vous pouvez enfin écrire du code général. Chez Badoo, nous considérons que c'est l'un des avantages les plus importants.
  2. Il est écrit en Kotlin, ce qui nous donne les avantages décrits ci-dessus: il n'y a aucune restriction sur null, variance / contravariance. Cela augmente la flexibilité et assure la sécurité lors de la compilation.
  3. Il n'y a aucune dépendance à l'égard d'autres bibliothèques, telles que RxJava, RxSwift, etc., ce qui signifie qu'il n'est pas nécessaire d'amener la fonctionnalité de bibliothèque à un dénominateur commun.
  4. API pure. Par exemple, l'interface ObservableSource dans Reaktive est simplement appelée Observable , et tous les opérateurs sont des fonctions d'extension situées dans des fichiers séparés. Il n'y a pas de classes de Dieu de 15 000 lignes. Cela permet d'augmenter facilement les fonctionnalités sans apporter de modifications aux interfaces et classes existantes.
  5. Prise en charge des planificateurs (en utilisant les observeOn familiers subscribeOn et observeOn ).
  6. Compatible avec RxJava2 (interopérabilité), fournissant la conversion de source entre Reaktive et RxJava2 et la possibilité de réutiliser les planificateurs de RxJava2.
  7. Conformité ReactiveX .

Je voudrais parler un peu plus des avantages que nous avons reçus du fait que la bibliothèque est écrite en Kotlin.

  1. Dans Reaktive, les valeurs nulles sont autorisées, car dans Kotlin c'est sûr. Voici quelques exemples intéressants:
    • 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) // ,

    La variation est également un gros avantage. Par exemple, dans l'interface Observable , le type T est déclaré comme out , ce qui permet d'écrire quelque chose comme ceci:

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

Voici à quoi ressemble la bibliothèque aujourd'hui:

  • état au moment de la rédaction: alpha (certains changements dans l'API publique sont possibles);
  • plateformes prises en charge: JVM et Android;
  • sources prises en charge: Observable , Maybe - Maybe , Single et Completable ;
  • un assez grand nombre d'opérateurs sont pris en charge, y compris map, filter, flatMap, concatMap, combineLatest, zip, merge et autres (la liste complète peut être trouvée sur GitHub );
  • Les ordonnanceurs suivants sont pris en charge: calcul, IO, trampoline et main;
  • sujets: PublishSubject et BehaviorSubject;
  • la contre-pression n'est pas encore prise en charge, mais nous réfléchissons à la nécessité et à la mise en œuvre de cette fonctionnalité.

Quels sont nos plans pour le futur proche:

  • commencer à utiliser Reaktive dans nos produits (nous envisageons actuellement des options);
  • Prise en charge de JavaScript (demande d'extraction déjà en cours d'examen);
  • Prise en charge iOS
  • publier des artefacts dans JCenter (qui utilise actuellement le service JitPack);
  • La documentation
  • augmentation du nombre d'opérateurs pris en charge;
  • Les tests
  • plus de plateformes - les demandes de tirage sont les bienvenues!

Vous pouvez essayer la bibliothèque maintenant, vous pouvez trouver tout ce dont vous avez besoin sur GitHub . Partagez votre expérience et posez des questions. Nous serons reconnaissants pour toute rétroaction.

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


All Articles