Lo sviluppo e l'integrazione di API RESTful o GraphQL per supportare architetture basate su microservizi.
APIs e microservizi rappresentano un importante punto di svolta nel mondo della programmazione, poiché consentono lo sviluppo di software scalabili, sicuri e semplici da aggiornare.
L’adozione di API e microservizi ha dato il via ad un nuovo modo di progettare software. O meglio, ha segnato il passaggio da un approccio rigido e monolitico ad un approccio modulare.
Nel corso dell’articolo mostreremo come queste tecnologie chiave hanno rivoluzionato il modo in cui le applicazioni vengono realizzate e distribuite.
Partiamo dal presentare una definizione di microservizi: singoli componenti di un’applicazione che contiene diversi servizi indipendenti tra loro e che svolgono una funzione specifica.
Dunque, quando facciamo riferimento a questo approccio architetturale e cloud native, sappiamo che ogni microservizio all’interno dell’applicazione è autonomo dagli altri, ma comunicano tra loro proprio grazie alle API.
Sono davvero numerosi i vantaggi di adottare l’architettura di microservizi rispetto a quella monolitica. In quest’ultimo caso, infatti, c’è una relazione di dipendenza tra i processi, eseguiti come un flusso singolo. Dunque, per aggiornare o modificare una funzionalità, bisognerebbe ritoccare l’intera architettura.
Per evitare rallentamenti e situazioni complesse da gestire, entrano in gioco i microservizi: costruendo servizi singoli e indipendenti che eseguono una funzione unica, è molto più semplice aggiornare, migliorare e integrare le applicazioni sviluppate.
Un team che predilige un’architettura di microservizi avrà la possibilità di lavorare più agilmente, poiché ognuno potrà scegliere diversi linguaggi di programmazione per far sì che ogni elemento contenga le capacità richieste.
Allo stesso tempo, un’applicazione così strutturata permette agli sviluppatori di creare ed integrare nuove funzionalità senza dover riscrivere il codice da zero. Ad esempio, se si sta lavorando allo sviluppo di un e-commerce e c’è bisogno di un componente dedicato ai pagamenti, sarà sufficiente integrare un microservizio dedicato alla gestione degli stessi.
Qual è, in questo caso, il ruolo del software developer? Semplicemente scrivere la parte di codice dedicata all’integrazione del microservizio con il sito web, ottimizzando sforzi e tempistiche.
API è un acronimo che sta per Application Programming Interface e, insieme ai microservices, è un elemento chiave per dotare di innovazione ed efficienza qualsiasi applicazione o sito web si voglia sviluppare.
Le API svolgono la funzione di intermediario tra i microservizi, quindi semplificano la comunicazione tra i componenti di un’app. Più semplicemente, queste garantiscono un collegamento sicuro ed efficace tra client - richiesta dell’applicazione - e server - la risposta elaborata in base alla richiesta.
Possiamo distinguere, in base all’architettura, diverse tipologie di API. Sicuramente le API RESTful sono le più diffuse oggi per l’efficienza che garantiscono, dove REST sta per “Representational State Transfer".
Questa tipologia di API presenta specifiche caratteristiche, come lo scambio di informazioni tramite HTTP (GET, POST, PUT, DELETE), ma anche un legame di indipendenza tra client e server.
Infatti, le API RESTful si contraddistinguono anche per la loro condizione stateless: i server non memorizzano lo stato delle richieste precedenti, poiché queste sono indipendenti tra loro e devono contenere tutte le informazioni necessarie per essere elaborate. Questo garantisce diversi benefici, come: maggiore scalabilità, affidabilità e sicurezza.
Ma non solo: un team che ha sviluppato un’app adottando API RESTful potrà modificare, alleggerire o aggiornare il codice con estrema semplicità. Questo perché fungendo da “ponte” tra due sistemi, una modifica da un lato non interferisce, in alcun modo, con l’altro.
In netto contrasto con l’architettura REST, abbiamo GraphQL: un linguaggio query per le API sviluppato da Facebook nel 2012 e reso pubblico nel 2015.
Il tratto distintivo di GraphQL è che consente di inviare richieste specifiche così da ottenere esclusivamente i dati interessati, ottimizzando le prestazioni ed evitando un possibile sovraccarico di informazioni.
Inoltre, fornisce tutti i dati in un endpoint unico che, tradotto in termini semplici, significa che queste API canalizzano l’invio/ricezione di tutti i dati in un unico URL.
Queste caratteristiche pongono l’accento sulla maggiore flessibilità e facilità d’uso offerta dalle API GraphQL, soprattutto se confrontate con le API RESTful.
Dopo aver compreso che i microservizi e le API sono elementi chiave per lo sviluppo di applicazioni sicure, “leggere” e avanzate, è importante delineare le principali differenze tra i due.
La prima differenza riguarda le ragioni che hanno spinto sempre più developers ad affidarsi a queste soluzioni. Nel caso dei microservizi, sappiamo che questa architettura consente la suddivisione di diversi componenti in appositi servizi. Si tratta di una caratteristica che facilita il lavoro, in quanto significa che ogni membro del team potrà lavorare allo sviluppo di un micro servizio che abbia specifiche funzionalità.
Le API, invece, sono delle interfacce che garantiscono l’interazione tra diversi software. Possiamo definire un’API come un contratto che definisce l’insieme di regole che consentono ad un’app di ricevere o accedere ai dati di un’altra applicazione.
Oltre ad una differenza di ruolo e funzioni, possiamo discutere anche del debug. Nel caso di microservizi, si tratta di un processo leggermente difficoltoso a causa della loro struttura architetturale e per l’adozione di stack e linguaggi diversi in base al servizio.
Per quanto riguarda le API, invece, il debug avviene con maggiore facilità sia perché presentano un'interfaccia ben definita che semplifica l'individuazione di eventuali errori, sia perché hanno un numero limitato di endpoint verso cui aggregare diverse funzionalità.
Abbiamo interrogato un backend developer di Unitiva che ci ha mostrato, step by step, come funziona l’integrazione tra microservizi e API:
1. Configurazione dell’ambiente di sviluppo
L’ambiente di sviluppo può essere quello che riteniamo più comodo usare. Bisogna scegliere quindi, in base alle esigenze del progetto uno stack tecnologico.
In questo caso, abbiamo scelto come tecnologie:
In realtà, si è rivelato cruciale anche l’utilizzo di Spring Web per includere servizi di tipo RESTful e Docker, così da poter creare l'immagine finale (e quindi del relativo container) del nostro microservizio.
Per la creazione di un progetto Spring Boot, è bene recarsi su start.spring.io e procedere alla creazione di un apposito progetto.
In questo caso, come detto poc’anzi, per questa dimostrazione è stato utilizzato Java 17, Spring Web, Spring Mail (Java Mail Sender nell’immagine). Il tipo di packaging è in JAR e come gestore di progetto è stato scelto Apache Maven.
Quest’ultimo è un tool per la gestione dei progetti e di build automation consigliato perché semplifica il lavoro di team e per la possibilità di automatizzare i compiti ripetitivi.
In questo caso, il nostro microservizio ha lo scopo di fungere da backend per un form di invio di email.
Assumendo che i dati necessari siano nome, cognome, email, oggetto e corpo del messaggio, procediamo con l’implementazione.
Essendo l’implementazione diversa a seconda delle nostre esigenze, analizzeremo soltanto i punti più comuni per quanto riguarda la parte di implementazione di un API e la build del progetto.
Volendo quindi tradurre le informazioni del form in una codifica leggibile al backend, scriviamo un file JSON (JavaScript Object Notation) da allegare successivamente alla chiamata verso il nostro API.
{ "your-name": "Mario", "your-surname": "Rossi", "your-email": "mariorossi@example.xyz", "message-subject": "Example", "message-body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." }
Procediamo quindi con la creazione di una view i cui dati corrispondono a quella della struttura dati sovrastante:
package it.unitiva.microservice-example.views; import com.fasterxml.jackson.annotation.JsonProperty; public class MailForm { @JsonProperty("your-name") private String yourName; @JsonProperty("your-surname") private String yourSurname; @JsonProperty("your-email") private String yourEmail; @JsonProperty("message-subject") private String messageSubject; @JsonProperty("message-body") private String messageBody; public MailForm() { } //getters and setters
Una volta realizzate tutte le funzionalità e servizi richiesti, bisogna implementare un’ API RESTful per permettere la comunicazione con un eventuale front-end;
Nell’implementazione di un’API è buona norma progettare, applicare e documentare una gestione degli errori in modo da fornire informazioni al client.
package it.unitiva.microservice-example.controller; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import it.unitiva.microserviceexample.service.mail.ContactUsMail; import it.unitiva.microserviceexample.views.MailForm; @RestController @RequestMapping("/api/v1/mails") public class MailController { @Autowired private ContactUsMail contactUsMail; private Logger LOG = LoggerFactory.getLogger(getClass()); @PostMapping("/contact_us") public ResponseEntitysendContactUsEmail(@RequestBody MailForm data) { if (data == null) { LOG.error("Cannot send mail. No Content provided"); return ResponseEntity.status(204).build(); } try { contactUsMail.sendContactUsEmail(data); LOG.info("Maildata: " + data); return ResponseEntity.status(200).build(); } catch (Exception exc) { LOG.error("Cannot send mail. Internal Server Error"); return ResponseEntity.status(500).build(); } } }
Infine, è consigliabile anche inserire dei log all’interno del backend in modo da fornire informazioni in runtime (seppur indicative) di ciò che accade durante la chiamata.
Per testare gli API in fase di sviluppo è necessario utilizzare un software in grado di inviare richieste HTTP. In questo caso, siccome stiamo lavorando sulla nostra macchina in locale, invieremo richieste HTTP verso l’URL: http://127.0.0.1:8080/api/v1/mails/contact_us. La richiesta sarà una POST, poiché abbiamo configurato l’API per accettarne solo di questa tipologia.
curl --request POST \ --url http://127.0.0.1:8080/api/v1/mails/contact_us \ --header 'Content-Type: application/json' \ --data '{ "your-name": "Mario", "your-surname": "Rossi", "your-email": "mariorossi@example.xyz", "message-subject": "Example", "message-body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." } ' Output del server frontend: PS C:\Users\Unitiva\Desktop\microservice-example> & 'C:\Program Files\Java\jdk-17\bin\java.exe' '@C:\Users\Unitiva\AppData\Local\Temp\cp_8hel5f3s30l13f7ddalfmgkej.argfile' 'it.unitiva.microserviceexample.MicroserviceExampleApplication' . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v3.1.9) 2024-02-26T10:29:02.202+01:00 INFO 21448 --- [ main] i.u.m.MicroserviceExampleApplication : Starting MicroserviceExampleApplication using Java 17.0.10 with PID 21448 (C:\Users\Unitiva\Desktop\microservice-example\target\classes started by Unitiva in C:\Users\Unitiva\Desktop\microservice-example) 2024-02-26T10:29:02.205+01:00 INFO 21448 --- [ main] i.u.m.MicroserviceExampleApplication : No active profile set, falling back to 1 default profile: "default" 2024-02-26T10:29:03.631+01:00 INFO 21448 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http) 2024-02-26T10:29:03.648+01:00 INFO 21448 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat] 2024-02-26T10:29:03.652+01:00 INFO 21448 --- [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.19] 2024-02-26T10:29:03.803+01:00 INFO 21448 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext 2024-02-26T10:29:03.803+01:00 INFO 21448 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1504 ms 2024-02-26T10:29:04.409+01:00 INFO 21448 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path '' 2024-02-26T10:29:04.423+01:00 INFO 21448 --- [ main] i.u.m.MicroserviceExampleApplication : Started MicroserviceExampleApplication in 2.771 seconds (process running for 3.429) 2024-02-26T10:41:19.183+01:00 INFO 21448 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet' 2024-02-26T10:41:19.183+01:00 INFO 21448 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet' 2024-02-26T10:41:19.184+01:00 INFO 21448 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 1 ms 2024-02-26T10:41:21.597+01:00 INFO 21448 --- [nio-8080-exec-1] i.u.m.controller.MailController : Maildata: MailForm [yourName=IlTuoNome, yourSurname=IlTuoCognome, messageSubject=Oggetto del Messaggio, messageBody=Contenuto del messaggio...] 2024-02-26T10:42:10.195+01:00 INFO 21448 --- [nio-8080-exec-2] i.u.m.controller.MailController : Maildata: MailForm [yourName=Mario, yourSurname=Rossi, messageSubject=Example, messageBody=Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.]
Nel caso in cui tale richiesta vada a buon fine (response code 200), verrà inviata una email con body in HTML verso un server SMTP.
Se il test viene superato e soddisfa le nostre aspettative possiamo passare alla fase di packaging.
La fase di packaging è una delle più importanti per realizzare un microservizio che funzioni a dovere non solo sul nostro ambiente locale.Per fare ciò ci siamo affidati ad uno strumento davvero potente: Docker.
Docker è una piattaforma open-source progettata per semplificare la creazione, la distribuzione e l'esecuzione di applicazioni all'interno di container.
Un container, invece, è un ambiente isolato che contiene tutto il necessario per eseguire un'applicazione, compresi il codice, le librerie e le dipendenze.
Dunque, Docker consente agli sviluppatori di confezionare un'applicazione e tutte le sue dipendenze in un unico contenitore, garantendo che essa funzioni in modo coerente su qualsiasi ambiente in cui venga eseguita.
É quindi possibile creare un’immagine del nostro progetto ed eventualmente caricarla in rete su un repository di docker hub e creare un’istanza del nostro software in base alle nostre esigenze.
Infine, per quanto possa sembrare banale, ricordiamo che un'attenta progettazione e un monitoraggio costante sono fondamentali per garantire la corretta funzionalità e l'affidabilità dei microservizi e delle API.
L’integrazione tra microservizi e API sta accomunando molte grandi aziende. Un esempio? Beh, sono parecchie le big del settore tech (ma non solo) che hanno scelto i microservices, come: Amazon, Twitter o Netflix.
Tutto ciò, ha reso queste aziende molto più agili. Nel caso di Amazon, ricordiamo che inizialmente il marketplace adottava una struttura monolitica. Successivamente, a causa di problemi riscontrati in termini di flessibilità, il team di sviluppo ha deciso di adottare un’architettura a microservizi, riuscendo a rispondere efficacemente alle richiesti dei clienti e garantendo una gestione più controllata delle funzionalità disponibili sull’e-commerce.
Ad oggi, i contesti d’uso in cui si applicano microservices e API sono in netta crescita. Tra questi, abbiamo:
L’adozione di queste tecnologie può offrire molteplici vantaggi competitivi ad un’organizzazione, impattando positivamente sul proprio business in termini di agilità, efficienza e scalabilità.