Reaktive - biblioteca multiplataforma para Kotlin reativo



Muitos hoje amam a programação reativa. Tem muitas vantagens: a falta do chamado " inferno de retorno de chamada ", o mecanismo interno de tratamento de erros e um estilo de programação funcional que reduz a probabilidade de erros. Significativamente mais fácil escrever código multiencadeado e mais fácil gerenciar fluxos de dados (combinar, dividir e converter).

Muitas linguagens de programação têm sua própria biblioteca reativa: RxJava para JVM, RxJS para JavaScript, RxSwift para iOS, Rx.NET etc.

Mas o que temos para Kotlin? Seria lógico supor que RxKotlin. E, de fato, existe uma biblioteca desse tipo, mas é apenas um conjunto de extensões para o RxJava2, o chamado "açúcar".

E, idealmente, eu gostaria de ter uma solução que atenda aos seguintes critérios:

  • multiplataforma - conseguir escrever bibliotecas multiplataforma usando programação reativa e distribuí-las na empresa;
  • Segurança nula - o sistema do tipo Kotlin nos protege de " erros de bilhões de dólares ", portanto valores nulos devem ser válidos (por exemplo, Observable<String?> );
  • covariância e contravariância é outro recurso muito útil do Kotlin, que possibilita, por exemplo, converter com segurança o tipo Observable<String> para Observable<CharSequence> .

Nós no Badoo decidimos não esperar o tempo à beira-mar e criamos uma biblioteca desse tipo. Como você deve ter adivinhado, chamamos Reaktive e postamos no GitHub .

Neste artigo, examinaremos mais de perto as expectativas de programação reativa de Kotlin e veremos como as capacidades do Reaktive as correspondem.

Três benefícios naturais de Reaktive


Multiplataforma


A primeira vantagem natural é mais importante. Atualmente, nossas equipes de iOS, Android e Mobile Web existem separadamente. Os requisitos são gerais, o design é o mesmo, mas cada equipe faz seu próprio trabalho.

O Kotlin permite escrever código multiplataforma, mas você precisa esquecer a programação reativa. E eu gostaria de poder escrever bibliotecas compartilhadas usando programação reativa e distribuí-las na empresa ou fazer upload no GitHub. Potencialmente, essa abordagem pode reduzir significativamente o tempo de desenvolvimento e a quantidade total de código.

Segurança nula


É mais sobre a falha do Java e do RxJava2. Em resumo, nulo não pode ser usado. Vamos tentar descobrir o porquê. Dê uma olhada nesta interface Java:

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

O resultado pode ser nulo? Para evitar ambiguidade, nulo não é permitido no RxJava2. E se você ainda precisar, é Talvez e Opcional. Mas em Kotlin não existem tais problemas. Podemos dizer que Single<User> e Single<User?> tipos diferentes e todos os problemas aparecem no estágio de compilação.

Covariância e contravariância


Esta é uma característica distintiva do Kotlin, algo que falta muito em Java. Você pode ler mais sobre isso no manual . Vou dar apenas alguns exemplos interessantes de quais problemas surgem ao usar o RxJava no Kotlin.

Covariância :

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

Como Observable é uma interface Java, esse código não é compilado. Isso ocorre porque tipos genéricos em Java são invariantes. É claro que você pode usar, mas usar operadores como o scan novamente levará a um erro de compilação:

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

A instrução de varredura é diferente, pois seu tipo genérico “T” é de entrada e saída. Se Observable fosse a interface Kotlin, seu tipo T poderia ser indicado como fora e isso resolveria o problema:

 interface Observable<out T> {   … } 

E aqui está um exemplo com contravariância:

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

Pelo mesmo motivo que no exemplo anterior (tipos genéricos em Java são invariáveis), este exemplo não é compilado. A adição resolverá o problema, mas novamente não cem por cento:

 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 } 

Bem, por tradição, no Kotlin esse problema é resolvido usando in na interface:

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

Assim, variabilidade e contravariância de tipos genéricos são a terceira vantagem natural da biblioteca Reaktive.

Kotlin + Reativo = Reaktive


Passamos à coisa principal - a descrição da biblioteca Reaktive.

Aqui estão alguns dos seus recursos:

  1. É multiplataforma, o que significa que você pode finalmente escrever código geral. No Badoo, consideramos esse um dos benefícios mais importantes.
  2. Está escrito em Kotlin, o que nos dá as vantagens descritas acima: não há restrições para nulo, variação / contravariância. Isso aumenta a flexibilidade e fornece segurança durante a compilação.
  3. Não há dependência de outras bibliotecas, como RxJava, RxSwift etc., o que significa que não há necessidade de levar a funcionalidade da biblioteca a um denominador comum.
  4. API pura. Por exemplo, a interface ObservableSource no Reaktive é chamada simplesmente Observable , e todos os operadores são funções de extensão localizadas em arquivos separados. Não há classes de Deus de 15.000 linhas. Isso torna possível aumentar facilmente a funcionalidade sem fazer alterações nas interfaces e classes existentes.
  5. Suporte para agendadores (usando observeOn familiares subscribeOn e observeOn ).
  6. Compatível com RxJava2 (interoperabilidade), fornecendo conversão de origem entre Reaktive e RxJava2 e a capacidade de reutilizar agendadores do RxJava2.
  7. Conformidade com o ReactiveX .

Gostaria de falar um pouco mais sobre os benefícios que recebemos devido ao fato de a biblioteca estar escrita em Kotlin.

  1. No Reaktive, valores nulos são permitidos, porque no Kotlin é seguro. Aqui estão alguns exemplos interessantes:
    • 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) // ,

    Variação também é uma grande vantagem. Por exemplo, na interface Observable , o tipo T é declarado como out , o que torna possível escrever algo como o seguinte:

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

É assim que a biblioteca se parece hoje:

  • status no momento da redação: alfa (são possíveis algumas alterações na API pública);
  • plataformas suportadas: JVM e Android;
  • fontes suportadas: Observable , Maybe , Single e Completable ;
  • um número bastante grande de operadores é suportado, incluindo map, filter, flatMap, concatMap, combineLatest, zip, merge e outros (a lista completa pode ser encontrada no GitHub );
  • Os seguintes agendadores são suportados: computação, IO, trampolim e principal;
  • assuntos: PublishSubject e BehaviorSubject;
  • a contrapressão ainda não é suportada, mas estamos pensando na necessidade e na implementação desse recurso.

Quais são nossos planos para o futuro próximo:

  • começar a usar o Reaktive em nossos produtos (atualmente estamos considerando opções);
  • Suporte a JavaScript (solicitação pull já em revisão);
  • suporte para iOS
  • publicar artefatos no JCenter (atualmente usando o serviço JitPack);
  • Documentação
  • aumento do número de operadores suportados;
  • Testes
  • mais plataformas - solicitações pull são bem-vindas!

Você pode experimentar a biblioteca agora e pode encontrar tudo o que precisa no GitHub . Compartilhe sua experiência e faça perguntas. Ficaremos gratos por qualquer feedback.

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


All Articles