Programação Reativa (Project Reactor + Spring WebFlux) em uma Arquitetura de Microsserviços

Marcelo M. Gonçalves
16 min readJan 8, 2020

--

O paradigma reativo (programação assíncrona) vem ganhando cada vez mais notoriedade por conta das promessas e benefícios adquiridos ao adotá-lo. A abordagem explora características no campo das linguagens de programação, porém não restrito a elas. Aplicar os princípios fundamentais presentes na programação assíncrona, compreendendo suas características centrais de funcionamento possibilita-nos a prática de um design mais inteligente (hardware/threads), naturalmente guiando-nos em direção a construção de sistemas mais responsivos e robustos.

A programação reativa possui o foco principal em trabalhar com a propagação de mudanças no fluxo de dados de forma assíncrona aumentando sua capacidade responsiva.

Ao tornar-se responsiva (Real-time), nossa aplicação precisa saber reagir a mudanças rapidamente, seja de carga (Load) ou orquestração a serviços externos (Downstream Services). Tornar-se reativo representa adquirir agilidade sobre qualquer mudança que afete nossa habilidade de atender uma demanda. Para nos tornarmos responsivos, precisamos direcionar nossos esforços rumo a elasticidade (Elastic), pois determinada característica permite ao sistema aumentar seu poder de processamento mantendo os índices de latência reduzidos.

Ao mesmo tempo em que a responsividade possui como característica principal a baixa latência, temos por outro lado a elasticidade exercendo da mesma forma grande influência sobre latência. Neste contexto, compreendemos o termo elasticidade como a habilidade de lidar com a carga de trabalho (Workload), aumentando a capacidade de processamento em alta demanda e reduzindo a capacidade de recursos em baixa demanda.

Reativo = Resiliente + Responsivo + Elástico

Ao construirmos sistemas reativos (Reactive Systems) promovemos o aumento de nossa capacidade responsiva. A responsividade acrescenta maior qualidade na experiência do usuário final. Desta forma, quanto maior a responsividade melhor será a experiência (Feeling + Feedback) sobre os componentes que entregaremos. Além disso, melhor será a capacidade de processamento (Throughput) em menor espaço de tempo. O Throughput replica a responsividade como resultado de ações tomadas em direção ao melhor tratamento do processo de carga atingindo maior elasticidade. Estratégias como escalabilidade horizontal e vertical servem como alternativas para aumento de elasticidade da aplicação.

Um sistema verdadeiramente responsivo apresenta naturalmente aspectos pertencentes ao Manifesto Reativo, possuindo características essenciais como Responsividade, Elasticidade (Resizing Load), Tolerância a falhas (Resilient), Isolamento e Escalabilidade (Horizontal ou Vertical), estando embarcado em uma arquitetura de comunicação Message-driven.

Outro conceito importante no contexto de responsividade trata-se da resiliência, sendo a característica de permanecer responsivo perante as falhas. Sem dúvida, o gerenciamento da resiliência trata-se de um critério de avaliação da qualidade, onde componentes funcionalmente resilientes são independentes e recuperam-se de suas falhas. Desta forma, qualquer aplicação que utilize o paradigma reativo adquire todas as vantagens citadas como competitiva ao negócio. Estes atributos são conquistados na prática empregando o princípio de comunicação Message-driven como estrutura base em sistemas distribuídos, referindo-se ao design de arquitetura e infraestrutura dos componentes.

Threads: Chamadas Síncronas e Assíncronas

Construir sistemas distribuídos e escaláveis trata-se de um enorme desafio. Mesmo empregando paradigmas Funcionais e Reativos, presentes em linguagens de programação (Java, Scala, Clojure) e Libraries (RxJava, Reactor, Akka Streams), amenizando a complexidade e curva de aprendizado, precisamos ainda nos preocupar com as naturezas de comunicação, modelo de retorno assíncrono não bloqueante, modelo de Threads, etc. O comportamento das Threads em um modelo de processamento síncrono são bloqueados pelo I/O, não podendo atender outras requisições no mesmo período em que aguardam o retorno da chamada anterior.

No processamento de requisições síncronas uma fatia dos recursos de CPU fica por conta de latências intrinsecamente relacionadas à chamadas bloqueantes. Operações que gerenciam com mais eficiência recursos de I/O utilizam modelos de interação assíncronos e não bloqueantes, pois o fator tempo é abordado com maior eficiência. Para trabalharmos com sistemas distribuídos, com design de arquitetura mais escalável e simplificado é necessário aplicar um estilo de comunicação baseada em eventos (Event-driven Communication). No contexto reativo, conceitos como Streaming e Backpressure (contrapressão) compõem a base para compreendermos aspectos essenciais inerentes ao paradigma.

Pipelines: Streams Assíncronos com Backpressure

O conceito de Streaming trata-se de uma eficiente técnica arquitetural a qual explora modelos de construção de fluxos de dados, reagindo a eventos na linha do tempo. Seu processamento e transformações de dados são realizadas em um Pipeline composto de Steps, conhecidos como Processors. O conceito de Backpressure refere-se a um mecanismo para gerenciamento de carga entre os passos em execução do pipeline em relação às chamadas aos Upstream Services (Publishers), impedindo sobrecarga entre os componentes (Subscribers).

Aplicações que utilizam um estilo de arquitetura com processamento de Streams — Reativos podem utilizar Windowing (janela de processamento) para atender o fluxo de forma inteligente, evitando Streams de dados infinitos. Pipelines reativos podem ser empregados em sistemas distribuídos com processamento multi-core. A compreensão do conceito reativo, do ponto de vista de infraestrutura, é importante para lidarmos com as complexidades como gerenciamento de Threads, recursos por I/O, assincronicidade com Callbacks, etc. Deste modo, mesmo Frameworks reativos (RxJava, Reactor) responsabilizando-se em gerenciar complexidades de hardware/infra, possuir conhecimentos em um nível mais baixo poderá fazer a diferença no sucesso de nossa aplicação.

O princípio Message-driven Pattern sugere que obrigatoriamente a comunicação deva ser não bloqueante. Técnicas de Callback podem ser aplicadas minimizando o bloqueio de recursos (Threads), sendo o resultado da execução devolvido posteriormente através de uma nova thread resgatada do pool, adquirindo um baixo acoplamento entre Caller Service e Called Service durante o processamento da requisição.

Uma API assíncrona oferece benefícios em relação a APIs síncronas, otimizando recursos de execução em Threads separadas. As funções de Callback são responsáveis por informar a conclusão do processamento de uma requisição. A programação assíncrona dos nossos fluxos permitem aos componentes serem mais ágeis e independentes, tornando-se mais concisos e menores facilitando sua manutenção.

JVM java.util.concurrent.Future: O Princípio

A interface Future, inserida a partir do Java 5, foi um passo importante para a programação assíncrona para JVM-Based Languages.. com a desvantagem de que, caso a requisição ainda não tenha sido finalizada (Upstream Service Call), sua instância implementando Future bloqueará a Thread até o término da execução (bloqueio de I/O).

Um dos principais conceitos promovidos por fluxos reativos são as Non-Blocking Operations. Sendo o oposto ao modelo Request-response (Síncrono/bloqueante).

Adquirindo assincronicidade através dos eventos, nenhum recurso fica preso dentro dos pipelines reativos. O processamento de chamadas externas (Upstream Services) é gerenciado de maneira mais efetiva, pois o modelo de Threads trabalha de forma mais eficaz em relação aos recursos de máquina. As implementações da interface Future (Java 5), CompletionStage, CompletableFuture (Java 8) foram utilizadas como referência para arquiteturas orientadas à mensagens, possibilitando comunicações mais robustas.

Avanços na evolução de Callbacks assíncronos foram obtidos no passado através de um melhor gerenciamento de I/O, evitando problemas relacionados ao controle manual de Threads.

O Observer Pattern, conceito mais antigo a utilizar Subjects gerando eventos de notificação aos Observers inscritos, sustenta a base de programação reativa moderna, marcando o início desta revolução. Portanto, trata-se de um processo evolutivo, contendo amadurecimento de ideias e abandono de outras. Ainda assim, após ter sido pioneiro em conceitos envolvendo Non-blocking, e ter contribuído com avanços atualmente refletidos em Libraries como RxJava 1.x, as classes Observer e Observable (Async-stream) encontram-se depreciadas a partir do Java/JDK 9.

Design Patterns: Observer, Publish-subscribe e RxJava

O Design Pattern Observer é composto por basicamente três elementos: Observer, Observable e Subject. Sendo assim, o Subject precisa conhecer os Observers, inscrevendo-se e possibilitando que notificações, por meio de eventos, sejam enviadas ao Observer.

No Publish-subscribe Pattern, publicadores e inscritos não necessitam conhecer um ao outro, favorecendo o baixo acoplamento. Diversos são os Frameworks que o implementam: Como exemplo, no contexto do Spring Framework, uma implementação do Publish-subscribe Pattern é fornecida através da Annotation @EventListener. Neste caso, possibilitando o roteamento de eventos sendo gerenciados em formato Topic-based ou Content-based pelo Spring.

A Library RxJava é frequentemente referenciada como uma combinação de Observer e Itarator Pattern, acrescentada a um estilo de programação funcional. RxJava trata-se de uma library para JVM, permitindo que blocos imperativos possam funcionar com Data-streams. No universo JVM, RxJava foi um dos primeiros esforços em direção a reatividade, modificando a maneira de como criamos aplicações reativas utilizando Java.

Desta forma, Observer + Itarator = RxJava (Reactive Stream). Ao passo em que no RxJava, a implementação consiste de um Observable Stream, um Subscriber e uma Subscription. O Subscription comunica a intenção do Subscriber em receber eventos de um Observable (Producer). Alguns operadores, pertencentes ao RxJava, destacam-se ao permitir-nos criar fluxos de negócio com pouco código.

Para citar algumas operações, RxJava .map(): Operador bastante utilizado, onde a transformação é aplicada item a item, tendo o Output Stream contendo o mesmo número de elementos que Input Stream. O RxJava .filter(), ao contrário do .map(): Pode produzir menos elementos do que ele recebeu, pois somente emite elementos que passaram em um teste de predicado. RxJava .count(): Emite a quantidade de elementos presentes no Input Stream, somente após o Input Stream original finalizar sua execução. RxJava .zip(): Recebe dois Parallel Stream aplicando uma operação de .merge() e gerando um Output Stream. Todas as funções citadas são possíveis graças ao suporte funcional (Lambdas) adicionados a Java/JVM a partir da versão 8, facilitando a criação de fluxos complexos e assíncronos utilizando programação reativa.

Elasticidade e Resiliência: Comunicação Message-driven

Arquiteturas baseadas em microsserviços podem aplicar fundamentos de elasticidade e resiliência, encontrando na programação assíncrona oportunidades de adoção de conceitos como: Message-driven Architecture, Event-channel, Message-broker ou Event-bus.

Mesmo seguindo especificações, bibliotecas reativas podem ser similares no comportamento geral, porém diferindo-se internamente. Devido a estas discrepâncias de implementação, não seria aconselhável misturá-las em uma mesma aplicação.

Com o avanço nos esforços no campo reativo, a Reactive Streams Specification foi criada visando padronizar estas implementações de Libraries que surgiam. A especificação define quatro interfaces primárias, Publisher (similar Observable), Subscriber (similar Observer), Subscription e Processor.

Reactive Streams provê o controle de Backpressure (controle de contrapressão) através de Subscription, expondo por meio da Request a quantidade de dados os quais o Subscriber possui capacidade para processar.

O conceito de comunicação assíncrona não bloqueante está naturalmente presente na maior parte das linguagens modernas, sendo o suporte por meio de Frameworks/Libraries cujo objetivo seja a otimização de de hardware (threads), evitando deixá-los ociosos.

A especificação Reactive Streams define que todos os sinais emitidos pelo Publisher e consumidos pelo Subscriber devem ser não bloqueados. Além disso, a especificação impõe uma regra de que a invocação de operações on** (next, publish, subscribe, error, complete, etc) devem ser Thread-safe pois serão executadas por múltiplas Threads paralelamente.

Dentro de fluxos reativos, entre os Producers e Consumers, existe uma subscrição que especifica o relacionamento entre eles, gerenciando a produção e consumo de eventos de maneira flexível. A programação assíncrona promove a noção de tempo valorizando a chegada do primeiro byte de dados, de forma mais versátil, impedindo o cliente de esperar até o final do processamento, entregando melhor experiência ao usuário.

O processamento de uma pipeline pode iniciar em uma thread, executar operações intermediárias em outra e finalizar sua execução em uma thread completamente diferente, provendo estratégias de imutabilidade e promovendo o desacoplamento temporal.

Programação reativa refere-se a uma mudança de mentalidade em determinados aspectos, dentre os quais destacam-se: Do paradigma imperativo para funcional, de natureza síncrona para assíncrona, do modelo de fluxo Pull (modelo tradicional) para Push (modelo reativo).

Tipos de Fluxo — Reativos: Hot Streams e Cold Streams

Temos os Hot Observable: Emitindo eventos independentemente do Observer estar pronto para consumo, necessitando controle de Backpressure. Cold Observable: Referindo-se a emissão de eventos sob demanda tendo o controle do fluxo solicitados pelo Subscriber.

Quando nos referimos ao paradigma reativo, mudamos o ponto de vista da maneira que construímos software. Seus conceitos fundamentais devem ser compreendidos a fundo, precisamos entender que chamadas nunca são bloqueantes em termos de recursos e I/O, que tudo são Streams com foco em transformação de dados, Streams não são Collections e possuem aspectos totalmente diferentes.

Programação reativa propõe-se a programar fluxos de dados utilizando fluxos assíncronos sem bloqueio de Threads (non-Blocking), tendo suas origens no estilo de programação assíncrona tradicional em relação ao seu modelo de concorrência.

Suporte Funcional Java 8: Lambdas e Imutabilidade

A inclusão dos Lambdas a partir do Java 8 promoveram a programação funcional, promovendo a imutabilidade dos dados e propondo-se a resolver diversos problemas relacionados ao paralelismo e concorrência entre as Threads em execução.

O fluxo reativo possui diversos In-Memory Stages, relacionados a sua execução e distribuídos entre diferentes Threads durante o processamento. Dentro do pipeline de execução In-Memory, os dados são processados de forma assíncrona sendo a comunicação efetuada por meio de mensagens. Desta forma os Processors presentes no fluxo dividem o processamento entre mensagens assíncronas com Threads independentes. Assim, podemos impor limites entre os Stages, possibilitando o paralelismo do fluxo.

Os Stages realizam a comunicação centralizando as mensagens trocadas durante o fluxo em uma Queue interna, desacoplando o controle do processo entre Publisher e Subscriber sem bloqueio de recursos de máquina.

Estratégias de Backpressure (contrapressão sob demanda) entram em cena quando o Producer não respeita a capacidade de consumo do Consumer, desta forma o controle da demanda de trabalho fica por conta do Subscriber (Pull-push). De forma que o Subscriber fica responsável por informar sua capacidade de trabalho ao Publisher, solicitando as próximas cargas (Pull) e impondo um controle de vazão (Push). Por parte do Publisher, este conceito evita que seu Subscriber seja inundado com eventos os quais não tenha capacidade de processar.

Java 9 implementou o Reactive Streams specification (especificação de fluxos reativos), trazendo um novo Pull-push Model (Flow API), melhorando o comportamento reativo e seguindo o modelo Event-loop Style.

Para seguirmos o conceito reativo em nossa aplicação, não podemos usar a abordagem tradicional Pulling-model para recebimento de dados, uma alternativa seria utilizarmos WebSockets ou Server-Sent Events (SSE). Neste caso, o recebimento de atualizações seriam enviadas automaticamente do servidor para o Browser, sincronizando os dados através do recurso EventSource (API JavaScript), o qual reconecta automaticamente com o servidor em caso de problemas com a conexão.

Spring: Project Reactor — Core e Fluxos Funcionais

Project Reactor trata-se de uma Library, sendo a mais popular/moderna implementação da Reative Stream Specification. Atualmente, fazendo parte do ecossistema Spring possuindo seu foco primário em legibilidade de código sem sacrificar a performance, encorajando a composição de Workflows (fluxos de trabalho) através da cadeia de operadores em pipelines funcionais.

Desta forma, nada acontece até que um Subscriber crie uma Subscription, que irá dispará o fluxo de dados (métodos como .block() ou .subscriber() podem disparar uma inscrição por parte do Subscriber), assim a comunicação por parte da Subscription é realizada entre Subscriber e Publisher (upstream) e adicionalmente, do Publisher para Subscriber (downstream).

Eventos estão em toda parte e são processados pelos Dispatchers, podendo ser síncronos ou assíncronos dependendo do tipo de implementação. Reactor 1.x possui habilidade para processamento de eventos em alta velocidade, assíncronos e não bloqueantes, integrando-se bem com Spring Framework e Netty.

Reactor 1.x não possui suporte a estratégias de Backpressure, bloqueando o Publisher e necessitando fazer Pulling de eventos. Reactor 3.x pode ser usado somente com Java 8+, liderando a base reativa existente no Spring Framework.

Reactor é agnóstico a concorrência, oferecendo Schedulers para processamento em paralelo do fluxo de dados no pipeline. No Project Reactor, as classes Flux e Mono são duas implementações da interface Publisher presente na Reactive Stream Specification. Containers de dados infinitos são naturais no estilo de programação reativa, nesse caso, os Subscribers podem cancelar a inscrição a qualquer momento, tornando o fluxo finito.

Dentro do fluxo de processamento do Pipeline, cada Operator dentro do fluxo de dados gera um novo Stream com base no Stream de entrada. Mono possui semântica semelhante ao CompletableFuture e pode ser utilizado em seu lugar. CompletableFuture não pode completar uma execução sem emitir um valor, iniciando o processamento imediatamente sem disparo de assinatura. Flux e Mono possuem diversos métodos (Operators) para criarmos Streams com base nos dados disponíveis, consumindo elementos produzidos por uma cadeia de Streams reativos.

Ao desenvolvermos nossos fluxos de dados reativos, Steps de transformação estão por toda a parte. Implementados pelos Processors (operadores), e desta forma, o mapeamento de eventos dentro de sequências reativas trata-se da forma mais natural de transformação de dados dentro do fluxo. Alguns operadores possuem maior índice de uso, popularidade e utilidade.

Quando se trata da implementação em Streams reativos, operadores como .map(), .flatMap(), .concatMap(), .flatMapSequential(), etc possuem alto índice de uso. O operador .map(Function<T, R>) mapeia e transforma cada elemento presente no Source para um novo elemento no Target, criando o elemento no destino e alterando seu tipo de T para R conforme assinatura do Operator (operador).

Tanto Flux quanto Mono provêm os operadores citados, possuindo comportamentos semelhantes do ponto de vista de implementação. O operador .map() pode ser utilizado tanto dentro de um Flux como de um Mono, gerando respectivamente transformações tanto de Flux<T> para Flux<R>, quanto de Mono<T> para Mono<R>. Neste contexto, Mono representa um Stream contendo um único sinal. Por outro lado, Flux é representado por um Stream contendo mais de um sinal emitido no Source.

No caso de necessitarmos de concatenação em nosso Pipeline, o operador .concat() concatena todos os elementos emitidos a partir dos canais Upstream(source) de entrada, somente iniciando a concatenação dos elementos subsequentes no Stream após a finalização do elemento anterior e assim por diante.

O operador .merge(), por outro lado, mistura os dados presentes nos Stream de entrada (Upstream) para o Stream de saída (Downstream). Diferente do operador .concat(), a inscrição (Subscriber) aos Upstreams Sources é realizada de forma imediata, simultaneamente, em todos os canais, não aguardando o processamento elemento a elemento como ocorre no operador .map().

O operador .zip() inscreve-se em todos os Upstreams de entrada, aguarda todos os elementos pertencentes ao Source Stream emitirem seus sinais para então combinar todos eles em um único elemento de saída (Output Stream). No Reactor, o operador .zip() pode operar não somente em Publishers reativos (Flux e Mono) mas também em objetos Iterable, disponibilizando para este propósito o operador .zipWithIterable().

O operador .flatMap(Function<T, Publisher<R>>) possui papel fundamental e crucial não somente no contexto reativo mas primeiramente no universo funcional. O .flatMap() trata-se de um dos operadores mais complexos de entender, porém, uma vez que realmente compreendemos como ele funciona, este operador acaba tornando-se uma poderosa ferramenta para auxiliar na construção de fluxos reativos.

Naturalmente, o Project Reactor não poderia deixar de oferecer uma implementação deste operador, bem como algumas variações como .flatMapSequential(). O operador .flatMap() consiste logicamente em duas operações executadas em conjunto: .map() e .merge().

Portando antes de entendermos o comportamento do .flatMap(), é necessário entender o operador .merge(). Dentro do .flatMap(), a etapa de .map(), transforma cada elemento de entrada em um Stream reativo de saída com ciclo de vida próprio. O .merge() por sua vez realiza a combinação dinâmica de de todas as sequências de Stream geradas em uma nova sequência (Stream) reativa, através do qual são passados elementos do tipo R transformados.

O comportamento interno dos operadores .flatMap() e .flatMapSequential() em relação aos Substreams gerados é inscrever-se instantaneamente e simultaneamente nestes novos Streams, passando a reagir aos eventos emitidos. O comportamento de inscrição instantânea do .flatMap() difere-se de operadores como .concatMap(), neste caso comportando-se de maneira oposta e somente inscrevendo-se no próximo Substream após o término (.onComplete() — onError()) do Stream antecedente.

Importante observar que o operador .concatMap() preserva a ordem dos elementos de origem por conta de sua natureza sequencial. Por outro lado, o operador .flatMap() não necessariamente preserva a ordem original por utilizar internamente o operador .merge(), intercalando os elementos dinamicamente. A saída, visando obter vantagens entre o melhor dos mundos, seria utilizar o operador .flatMapSequential(), preservando a ordem dos elementos originais e realizando o controle de ordenação por meio de uma Queue (fila) interna.

O principal caso de uso para o operador .flatMap() seria efetuar chamadas assíncronas não bloqueantes com promessa de retorno (Callback) via Promise (promessa). Assim, o retorno da chamada seria garantido em algum momento desconhecido no tempo, tendo seu retorno processado efetivamente em diferentes Threads de forma assíncrona, possibilitando o retorno ao fluxo principal do Pipeline a partir do Step ao qual a chamada ao .flatMap() foi realizada.

Spring: Web MVC e Webflux

Spring Framework, particularmente a partir da versão 5, embarcou diversos módulos úteis e poderosos, dentre os quais destaca-se o módulo WebFlux para criação de aplicações reativas. Para possibilitar a utilização do paradigma reativo dentro das aplicações que rodam na JVM, Spring implementa o Reactive Stream Specification (especificação para fluxos reativos) por meio do Project Reactor.

Ficando o módulo Spring WebFlux responsável por prover suporte a construção de aplicações reativas do lado do servidor. Além disso, não necessitamos do Servlet API para rodarmos uma aplicação WebFlux, substituindo-os por Reactive Streams / HTTP, nem do Tomcat Servlet Container ao utilizarmos Netty Server.

A partir da versão 3.1 do Servlet API, a implementação pelo Spring Framework suporta chamadas assíncronas dentro do módulo Web MVC, porém existem diversos problemas na implementação destas características. Nem todas as funcionalidades estão presentes na especificação non-Blocking Servlet API, por esta razão não podendo ser considerado um Framework para altas cargas de dados em Real-time.

A implementação provida pelo WebFlux é capaz de processar múltiplas Async non-Blocking Calls (chamadas assíncronas não bloqueantes) paralelamente. Não existe uma opção para realizarmos chamadas non-Blocking dentro do ciclo de vida de uma Request utilizando Spring Web MVC. Além disso, o módulo não traz um HTTP Client non-Blocking Out-out-the-box (pronto para uso), como consequência a maior parte das chamadas aos serviços externos causará bloqueio de recursos (Threads por I/O).

O módulo WebFlux utiliza o design existente do Web MVC, aplicando mudanças mínimas de código e potencialmente reutilizando boa parte. Utilizando WebFlux, garantimos um modelo de design reativo em direção a um módulo Web Stack, oferecendo uma experiência realmente reativa.

No contexto reativo, uma das etapas mais cruciais trata-se do modelo de interação via Stream, onde ambos, cliente e servidor podem emitir e reagir por meio de fluxos de dados.

Nesta tarefa, o protocolo para comunicação WebSocket destaca-se, sendo uma implementação do padrão Duplex (Client-server Communication) em uma versão melhorada, oferecida pelo Spring Framework. O WebFlux oferece a infraestrutura necessária para que cliente e servidor possam comunicar-se obtendo benefício deste modelo de comunicação (WebClient).

Spring Web MVC mostrou-se ao longo dos anos um Framework estável, formando uma base sólida para criação de aplicações Web. Além disso, tornou o Spring Framework uma das mais populares soluções para desenvolvimento de Web Applications (aplicações web) que utilizam a Servlet API.

Contudo, tecnologias antigas não atendem bem os requisitos de sistemas modernos, que possuem consumo intensivo de dados em Real-time (tempo real), abrindo margem para necessidade de implementações mais responsivas.

Uma das principais diferenças entre os modelos Web MVC e WebFlux é que o Design do Spring Web MVC confia em uma estrutura de Servlet Container, de alta latência pois utiliza chamadas síncronas bloqueantes.

No Web MVC, o componente responsável por controlar e mapear todas as requisições (HttpServletRequest) e respostas (HttpServletResponse) através de interação direta (Binding and Validation), trata-se do DispatchServlet.

O módulo WebFlux substitui o modelo de comunicação síncrono presente no Web MVC através dos tipos reativos Flux, Mono, e Publisher acoplados às assinaturas dos métodos, possibilitando assim dois modelos de programação Annotation-based (modelo tradicional) e Functional-routing (componentes declarados via funções).

Ao mesmo tempo em que WebFlux possui diversas similaridades com Web MVC ele acrescenta diversas outras funcionalidades. Spring Web MVC possui como característica chave o modelo de programação baseado em anotações, modelo o qual foi conservado pelo WebFlux.

Web MVC necessita de uma Thread por requisição HTTP, pois trata-se de uma implementação da Servlet API. Por outro lado, o módulo WebFlux utiliza o Event-loop Pattern, herdado do NodeJS, promovendo o comportamento assíncrono, aumentando o ganho de escalabilidade e resiliência.

Considerações Finais

A adoção de modelos reativos em implementações de Back-end já é uma realidade, podemos perceber através do esforço de diversas empresas (Pivotal, Red Hat, IBM, Google, etc) em direção ao sucesso de entregarmos aplicações cada vez mais resilientes às complexidades encontradas em aplicações distribuídas (ex: mini, micro serviços).

Independente da escolha de tecnologias, aderentes aos negócios de cada empresa, é preciso tornar-se cada vez mais responsivo, melhorando a experiência do usuário e consequentemente elevando nossas aplicações ao patamar reativo.

--

--