Padroneggiare il framework 5 di Spring, Parte 2: Spring WebFlux

Spring WebFlux introduce lo sviluppo web reattivo nell'ecosistema Spring. Questo articolo ti consentirà di iniziare con i sistemi reattivi e la programmazione reattiva con Spring. Per prima cosa scoprirai perché i sistemi reattivi sono importanti e come vengono implementati in Spring Framework 5, quindi riceverai un'introduzione pratica alla creazione di servizi reattivi utilizzando Spring WebFlux. Costruiremo la nostra prima applicazione reattiva usando le annotazioni. Ti mostrerò anche come creare un'applicazione simile utilizzando le nuove funzionalità funzionali di Spring.

Tutorial di primavera su JavaWorld

Se sei nuovo nel framework Spring, ti consiglio di iniziare con uno dei tutorial precedenti di questa serie:

  • Cos'è la primavera? Sviluppo basato su componenti per Java
  • Mastering Spring framework 5: Spring MVC

Sistemi reattivi e Spring WebFlux

Il termine reattivo è attualmente popolare tra sviluppatori e responsabili IT, ma ho notato qualche incertezza su cosa significhi effettivamente. Per essere più chiari su cosa sono i sistemi reattivi, è utile comprendere il problema fondamentale per cui sono progettati. In questa sezione parleremo dei sistemi reattivi in ​​generale e introdurrò l'API Reactive Streams per le applicazioni Java.

Scalabilità in Spring MVC

Spring MVC si è guadagnata un posto tra le migliori scelte per la creazione di applicazioni Web e servizi Web Java. Come abbiamo scoperto in Mastering Spring framework 5, Parte 1, Spring MVC integra perfettamente le annotazioni nella solida architettura di un'applicazione basata su Spring. Ciò consente agli sviluppatori che hanno familiarità con Spring di creare rapidamente applicazioni Web soddisfacenti e altamente funzionali. Tuttavia, la scalabilità è una sfida per le applicazioni Spring MVC. Questo è il problema che Spring WebFlux cerca di affrontare.

Framework web bloccanti e non bloccanti

Nelle applicazioni Web tradizionali, quando un server Web riceve una richiesta da un client, accetta tale richiesta e la colloca in una coda di esecuzione. Un thread nel pool di thread della coda di esecuzione riceve quindi la richiesta, legge i suoi parametri di input e genera una risposta. Lungo il percorso, se il thread di esecuzione deve chiamare una risorsa di blocco, come un database, un file system o un altro servizio Web, quel thread esegue la richiesta di blocco e attende una risposta. In questo paradigma il thread viene effettivamente bloccato fino a quando la risorsa esterna non risponde, il che causa problemi di prestazioni e limita la scalabilità. Per combattere questi problemi, gli sviluppatori creano pool di thread di dimensioni generose, in modo che mentre un thread è bloccato, un altro thread può continuare a elaborare le richieste. La Figura 1 mostra il flusso di esecuzione per un'applicazione Web tradizionale di blocco.

Steven Haines

I framework web non bloccanti come NodeJS e Play adottano un approccio diverso. Invece di eseguire una richiesta di blocco e attendere il completamento, utilizzano I / O non bloccanti. In questo paradigma, un'applicazione esegue una richiesta, fornisce il codice da eseguire quando viene restituita una risposta e quindi restituisce il thread al server. Quando una risorsa esterna restituisce una risposta, verrà eseguito il codice fornito. Internamente, i framework non bloccanti operano utilizzando un ciclo di eventi. All'interno del ciclo, il codice dell'applicazione fornisce una richiamata o un futuro contenente il codice da eseguire al termine del ciclo asincrono.

Per natura, i framework non bloccanti sono guidati dagli eventi . Ciò richiede un paradigma di programmazione diverso e un nuovo approccio al ragionamento su come verrà eseguito il codice. Una volta che ci hai pensato bene, la programmazione reattiva può portare ad applicazioni molto scalabili.

Richiami, promesse e futures

All'inizio, JavaScript gestiva tutte le funzionalità asincrone tramite callback . In questo scenario, quando si verifica un evento (come quando diventa disponibile una risposta da una chiamata di servizio) viene eseguita la richiamata. Sebbene i callback siano ancora prevalenti, la funzionalità asincrona di JavaScript si è recentemente spostata sulle promesse . Con le promesse, una chiamata di funzione ritorna immediatamente, restituendo una promessa per fornire i risultati in un momento futuro. Piuttosto che promesse, Java implementa un paradigma simile utilizzando il futuro . In questo utilizzo, un metodo restituisce un futuro che avrà un valore in un momento futuro.

Programmazione reattiva

Potresti aver sentito il termine programmazione reattiva relativo a framework e strumenti di sviluppo web, ma cosa significa veramente? Il termine come siamo venuti a sapere ha avuto origine dal Manifesto Reattivo, che definisce i sistemi reattivi come aventi quattro tratti principali:

  1. I sistemi reattivi sono reattivi , nel senso che rispondono in modo tempestivo, in tutte le circostanze possibili. Si concentrano sulla fornitura di tempi di risposta rapidi e coerenti, stabilendo limiti superiori affidabili in modo da fornire una qualità del servizio costante.
  2. I sistemi reattivi sono resilienti , il che significa che rimangono reattivi di fronte al fallimento. La resilienza si ottiene con le tecniche di replica, contenimento, isolamento e delega. Isolando i componenti dell'applicazione l'uno dall'altro, è possibile contenere gli errori e proteggere il sistema nel suo complesso.
  3. I sistemi reattivi sono elastici , il che significa che rimangono reattivi a carichi di lavoro variabili. Ciò si ottiene scalando elasticamente i componenti dell'applicazione per soddisfare la domanda corrente.
  4. I sistemi reattivi sono guidati dai messaggi , il che significa che si basano sul passaggio di messaggi asincrono tra i componenti. Ciò consente di creare accoppiamenti sciolti, isolamento e trasparenza della posizione.

La figura 2 mostra come questi tratti fluiscono insieme in un sistema reattivo.

Steven Haines

Caratteristiche di un sistema reattivo

I sistemi reattivi vengono costruiti creando componenti isolati che comunicano tra loro in modo asincrono e possono scalare rapidamente per soddisfare il carico corrente. I componenti continuano a guastarsi nei sistemi reattivi, ma ci sono azioni definite da eseguire a seguito di tale guasto, il che mantiene il sistema nel suo complesso funzionale e reattivo.

Il Manifesto Reattivo è astratto, ma le applicazioni reattive sono tipicamente caratterizzate dai seguenti componenti o tecniche:

  • Flussi di dati : uno stream è una sequenza di eventi ordinati nel tempo, come interazioni utente, chiamate al servizio REST, messaggi JMS e risultati da un database.
  • Asincrono : gli eventi del flusso di dati vengono acquisiti in modo asincrono e il codice definisce cosa fare quando viene emesso un evento, quando si verifica un errore e quando il flusso di eventi è stato completato.
  • Non bloccante : durante l'elaborazione degli eventi, il codice non deve bloccare ed eseguire chiamate sincrone; invece, dovrebbe effettuare chiamate asincrone e rispondere quando vengono restituiti i risultati di tali chiamate.
  • Contropressione : i componenti controllano il numero di eventi e la frequenza con cui vengono emessi. In termini reattivi, il componente viene indicato come abbonato e gli eventi vengono emessi da un editore . Questo è importante perché l'abbonato ha il controllo della quantità di dati che riceve e quindi non si sovraccaricherà.
  • Messaggi di errore : invece di componenti che generano eccezioni, gli errori vengono inviati come messaggi a una funzione del gestore. Mentre la generazione di eccezioni interrompe il flusso, la definizione di una funzione per gestire gli errori non appena si verificano.

L'API Reactive Streams

La nuova API Reactive Streams è stata creata da ingegneri di Netflix, Pivotal, Lightbend, RedHat, Twitter e Oracle, tra gli altri. Pubblicata nel 2015, l'API Reactive Streams fa ora parte di Java 9. Definisce quattro interfacce:

  • Editore : invia una sequenza di eventi ai sottoscrittori.
  • Sottoscrittore : riceve ed elabora gli eventi emessi da un editore.
  • Sottoscrizione : definisce una relazione uno a uno tra un editore e un sottoscrittore.
  • Processore : rappresenta una fase di elaborazione composta sia da un Sottoscrittore che da un Editore e obbedisce ai contratti di entrambi.

La figura 3 mostra la relazione tra un editore, un abbonato e un abbonamento.

Steven Haines

In sostanza, un Sottoscrittore crea una Sottoscrizione a un Editore e, quando l'editore ha dati disponibili, invia un evento al Sottoscrittore con un flusso di elementi. Si noti che l'abbonato gestisce la sua contropressione all'interno della sua sottoscrizione al publisher.

Ora che conosci un po 'dei sistemi reattivi e dell'API Reactive Streams, rivolgiamo la nostra attenzione agli strumenti che Spring utilizza per implementare i sistemi reattivi: Spring WebFlux e la libreria Reactor.

Project Reactor

Project Reactor è un framework di terze parti basato sulla specifica Reactive Streams di Java, che viene utilizzato per creare applicazioni web non bloccanti. Project Reactor fornisce due editori ampiamente utilizzati in Spring WebFlux:

  • Mono : restituisce 0 o 1 elemento.
  • Flusso : restituisce 0 o più elementi. Un Flux può essere infinito, il che significa che può continuare a emettere elementi per sempre, oppure può restituire una sequenza di elementi e quindi inviare una notifica di completamento quando ha restituito tutti i suoi elementi.

I monos e i flussi sono concettualmente simili ai futuri, ma più potenti. Quando si richiama una funzione che restituisce un mono o un flusso, verrà restituita immediatamente. I risultati della chiamata di funzione ti verranno consegnati tramite mono o flux quando saranno disponibili.

In Spring WebFlux, chiamerai librerie reattive che restituiscono monos e flux ei tuoi controller restituiranno monos e flux. Poiché questi ritornano immediatamente, i controller rinunceranno effettivamente ai loro thread e consentiranno a Reactor di gestire le risposte in modo asincrono. È importante notare che solo utilizzando librerie reattive i tuoi servizi WebFlux possono rimanere reattivi. Se si utilizzano librerie non reattive, come le chiamate JDBC, il codice si bloccherà e attenderà il completamento di tali chiamate prima di tornare.

Programmazione reattiva con MongoDB

Attualmente, non ci sono molte librerie di database reattive, quindi potresti chiederti se è pratico scrivere servizi reattivi. La buona notizia è che MongoDB ha un supporto reattivo e ci sono un paio di driver di database reattivi di terze parti per MySQL e Postgres. Per tutti gli altri casi d'uso, WebFlux fornisce un meccanismo per eseguire chiamate JDBC in modo reattivo, anche se utilizzando un pool di thread secondario che effettua il blocco delle chiamate JDBC.

Inizia con Spring WebFlux

Per il nostro primo esempio pratico, creeremo un semplice servizio di libri che mantiene i libri da e verso MongoDB in modo reattivo.

Inizia accedendo alla home page di Spring Initializr, dove sceglierai un progetto Maven con Java e seleziona la versione più recente di Spring Boot (2.0.3 al momento della stesura di questo documento). Assegna al progetto un nome di gruppo, ad esempio "com.javaworld.webflux", e un nome di artefatto, ad esempio "bookservice". Espandere il collegamento Passa alla versione completa per visualizzare l'elenco completo delle dipendenze. Seleziona le seguenti dipendenze per l'applicazione di esempio:

  • Web -> Reactive Web : questa dipendenza include Spring WebFlux.
  • NoSQL -> Reactive MongoDB : questa dipendenza include i driver reattivi per MongoDB.
  • NoSQL -> MongoDB incorporato : questa dipendenza ci consente di eseguire una versione incorporata di MongoDB, quindi non è necessario installare un'istanza separata. Di solito viene utilizzato per i test, ma lo includeremo nel nostro codice di rilascio per evitare di installare MongoDB.
  • Core -> Lombok : l'utilizzo di Lombok è facoltativo in quanto non è necessario per creare un'applicazione Spring WebFlux. Il vantaggio di utilizzare Progetto Lombok è che consente di aggiungere annotazioni alle classi che generano automaticamente getter e setter, costruttori, hashCode(), equals(), e altro ancora.

Quando hai finito dovresti vedere qualcosa di simile alla Figura 4.

Steven Haines

Premendo Genera progetto si attiverà il download di un file zip contenente il codice sorgente del progetto. Decomprimi il file scaricato e aprilo nel tuo IDE preferito. Se stai usando IntelliJ, scegli File, quindi Apri e vai alla directory in cui il file zip scaricato è stato decompresso.

Scoprirai che Spring Initializr ha generato due file importanti:

  1. Un pom.xmlfile Maven , che include tutte le dipendenze necessarie per l'applicazione.
  2. BookserviceApplication.java, che è la classe di avvio Spring Boot per l'applicazione.

Il Listato 1 mostra il contenuto del file pom.xml generato.