ReactiveX — Transformando sua linguagem favorita
No mundo de desenvolvimento de software, geralmente existem várias maneiras de resolver o mesmo problema. De tempos em tempos, surgem tecnologias que clamam melhorar a vida do desenvolvedor de software. Um exemplo disso é a programação reativa, paradigma computacional que tem como principal finalidade resolver problemas relacionados a assincronia.
O objetivo deste post é apresentar o ReactiveX, esclarecer quando é bom (ou não) usar e entender os conceitos básicos sobre este paradigma.
O que é programação reativa?
Existem diferentes paradigmas de programação, talvez o mais comumente utilizado é o imperativo. Neste paradigma, uma expressão é avaliada uma vez e um valor é atribuído a uma variável:
var x = 2
var y = 4
var z = x * y // z é 8
x = 10
// z ainda é 8
Por outro lado, a programação reativa trata de responder às mudanças de valor. Provavelmente você já a usou alguma vez — mesmo que não tenha percebido isso na época. Para exemplificar o seu funcionamento, vamos fazer um paralelo com o funcionamento de uma planilha eletrônica:
- Definir valores nas células é semelhante à definição de variáveis na programação imperativa.
- Definir expressões é semelhante à definição de Observables (não se preocupe, logo falaremos sobre eles) na programação reativa.
Na planilha, a célula B1 está atribuída com o valor 2 e a B2 com 4. Já a célula B3 possui uma expressão que multiplica B1 por B2. Quando o valor de um das células referenciadas na expressão é alterado, a alteração é “observada” e a expressão é reavaliada automaticamente em B3.
Outra característica notável deste paradigma é a capacidade de reagir a fluxos de dados assíncronos. Se você já se aventurou no mundo frontend, existe um bom exemplo para ilustrar esta característica. Estou falando de componentes como o autocomplete. Como funciona?
O usuário começa a digitar, quando ele termina, um fluxo de caracteres é processado pelo componente. Quando pensamos no mundo backend, essa característica ajuda em dois casos distintos:
- Caso 1: Você precisa consumir um fluxo de dados que é lento, que surge pouco a pouco. Por exemplo, imagine que você implementou uma aplicação que monitora a hashtag #reactiveprogramming no Twitter e quer tomar ações conforme novos tweets surgem.
- Caso 2: Você precisa consumir um fluxo de dados massivo, mas sua aplicação não tem recursos para isso. Neste caso, os operadores Backpressure podem ajudar. Simplificando bastante a explicação, estes operadores são um mecanismo de defesa para evitar que sua aplicação pare de responder devido ao grande volume de dados.
O que é ReactiveX?
Certo, agora que já entendemos o que é a programação reativa, o que é esse tal de ReactiveX?
Reactive Extensions (ReactiveX ou Rx) é um conjunto de bibliotecas que segue os princípios da programação reativa. Essas bibliotecas fornecem um conjunto de interfaces e métodos que ajudam os desenvolvedores a escrever códigos limpos e simples. No momento que escrevo este post, o Rx está disponível em 18 linguagens, incluindo Java, Javascript, C#, Python e outras.
Quando devo usar?
A programação reativa demonstra seu verdadeiro potencial quando sua aplicação possui a necessidade de tratar dados de fontes paralelas ou de forma assíncrona. Desenvolver uma aplicação com concorrência normalmente é um trabalho que exige um esforço acima da média.
Durante o passar dos anos, muitas linguagens criaram mecanismos para facilitar a vida do desenvolvedor neste sentido, e gosto de pensar que a programação reativa é mais um passo nessa evolução. Vamos listar alguns benefícios dela:
Evita o “callback hell”. Em simples palavras, callback é uma técnica de programação que você passa um código por argumento o qual será executado em um determinado momento. O callback foi uma das técnicas que nasceram para resolver problemas de assincronia. Quando utilizado com parcimônia, é muito útil e atende a necessidade com elegância. O problema dele começa quando precisamos aninhar várias chamadas, esse é o “callback hell”. Para ilustrar, segue abaixo um exemplo:
Mais simples de programar threads. O callback é um problema muito comum em linguagens de natureza assíncrona (Node.js, Erlang, Elixir). Contudo, as linguagens de natureza não assíncrona também podem se beneficiar deste paradigma. Principalmente quando há a necessidade de uso de threads para fazer processamentos em paralelo. Utilizando o método convencional, gerenciar e orquestrar múltiplas threads pode ser um verdadeiro desafio.
Código limpo e legível. Outro benefício que ela traz é a simplicidade e legibilidade para o seu código. Apesar de a curva de aprendizado ser relativamente alta, uma vez que o desenvolvedor está com o mindset adequado, o código fica muito legível ao ponto de como uma sentença em inglês, principalmente quando utilizamos Operators (já vamos ver mais sobre eles) de forma encadeada. Abaixo um simples exemplo:
Note que em vez de ditar como algo deve ser feito — como ocorre na programação imperativa — este paradigma usa uma abordagem declarativa, descrevendo o que deve ser feito. Esse estilo declarativo mapeia muito mais de perto como nós, como humanos, pensamos (em oposição a como um computador executa), e nos permite raciocinar muito mais facilmente sobre o que o programa está fazendo.
Conceitos básicos: Observable, Observer e Operator
A programação reativa é uma junção dos padrões de projeto Observer e Iterator com o paradigma funcional. Os componentes principais dela são os três O’s: Observable, Observer e Operator.
O Observable (observável
) é uma entidade que emite itens ao longo do tempo. Ele pode ser subscribed (subscrito
) por um Observer (observador
) que então receberá os itens que foram enviados. Um Operator (operador
), ou uma sequência deles, pode então ser inserido entre o Observable e o Observer para performar ações que transformam, filtram, agregam e combinam os dados emitidos pelo Observable.
Gosto de fazer paralelos com o mundo real. Usando como exemplo uma palestra internacional com tradução simultânea, temos três entidades:
- O palestrante que está transmitindo seu conhecimento, ele é o Observable.
- O interprete, que está processando o conhecimento emitido pelo palestrante e transformando (traduzindo) para uma linguagem especifica, ele é o Operator.
- O ouvinte que está recebendo a informação processada pelo tradutor, ele é o Observer.
Quando não devo usar?
Agora que vimos as vantagens, fica o questionamento: será devemos começar a utilizar somente este paradigma na criação de nossos softwares? Certamente a resposta é não. Assim como qualquer outra tecnologia, ela não é a bala de prata e quando utilizada de modo errado vai somente aumentar a complexidade da sua aplicação desnecessariamente.
A programação reativa — assim como a funcional — possui uma curva de aprendizado bem grande, isso ocorre principalmente devido à necessidade de mudança do modo de pensar para solução dos problemas. Por isso, é importante entender bem quando realmente quando sua aplicação é benéfica. Pois, ao fazer isso, a probabilidade de adicionar complexidade para novos desenvolvedores é bem grande. Abaixo vamos ver alguns casos que o uso da programação reativa pode não ser o recomendado — e ser considerado overengineering:
Caso 1: Conjunto de dados pequeno e/ou constante
O Rx permite transformar basicamente qualquer coisa em um Observable, mas isso não quer dizer que devemos fazer isso. Um exemplo de quando não devemos é para os casos que o conjunto de dados é pequeno e/ou constante. Nestes casos, o trade-off pode não valer a pena.
Caso 2: Consultas simples
O Rx é ótimo na habilidade de definir buscas complexas, com vários passos. Pegue isso, então filtre aquilo, depois transforme neste formato e converta nisto. Com ele, é possível criar uma longa e elaborada cadeia de operações com relativamente pouco código.
Contudo, faz sentido criar um Observable para uma simples consulta?
Há casos que pode fazer sentido, mas é sempre bom se questionar para evitar criar códigos boilerplate.
Caso 3: Objetos custosos
Imagine o seguinte caso, criamos a classe RegularExpressionProcessor
, ela é responsável por executar uma lista de expressões regulares (algo que é muito custoso). Agora, digamos que criamos um Observable que ira criar instâncias do RegularExpressionProcessor
a partir de registros de um banco de dados. Dependendo do volume de registros, este processamento pode ser exponencialmente caro, já que cada subscriber irá fazer com que todos as expressões regulares sejam reexecutadas. Claro que podemos resolver este problema utilizando mecanismos de cache (por exemplo). Contudo, será que não seria melhor ter uma simples lista?
Caso 4: Necessidade de manter o estado do seu objeto
O Rx ganhou como herança da programação funcional a habilidade de ser stateless, isso significa que ele não mantem ou depende de um estado. Essa é uma característica que auxilia no desenvolvimento de sistema multi-thread. Sendo assim, se você está desenvolvendo algo que depende de estado. Há uma grande possibilidade de que a programação reativa não seja a solução ideal.
E agora?
A programação reativa — assim como a funcional — possui uma curva de aprendizado relativamente alta. Contudo, é um conhecimento que vale a pena adquirir, mesmo que não seja necessário no seu dia-a-dia de trabalho. Uma das razões é por mudar o modo que você pensa na solução do problema.
Os padrões de projetos (como o Observer e o Iterator citados neste post) é outro conhecimento muito valioso para um desenvolvedor de sofware. Saber quando utilizar determinados padrões de projetos é uma importante ferramenta para ter. Caso seja do seu interesse estudar um pouco mais sobre, recomendo ler o livro “Agile Principles, Patterns, and Practices in C#”.
Nas próximas semanas pretendo fazer um post explorando um pouco mais o ReactiveX, mostrando um pouco de exemplo de código, mais especificamente com RxJava e RxAndroid.
Se quiser trocar uma ideia ou entrar em contato comigo, pode me achar no Twitter(@e_ferreirasouza) ou Linkedin.
Grande abraço e até a próxima!