Alzi la mano chi negli ultimi sei mesi non ha sentito nominare il termine Microservizio!

On-premise o sul Cloud, containerized o meno, i Microservices rappresentano oggi un serio concorrente dell’altrettanto abusato Machine Learning per il titolo di “Best Buzzword – 2017 edition”. Fuori dall’ironia: come accade per ogni tecnologia realmente trasformativa, è bene non farsi contagiare dall’hype dirompente. Vale piuttosto la pena sondare con occhio critico i punti di forza e le criticità di questo nuovo approccio, per verificare la sua effettiva applicabilità al proprio contesto aziendale – ed eventualmente approntare un piano di migrazione sostenibile.

Nella prima parte di questo articolo viene fornita una rapida introduzione al mondo dei Microservices e delle Serverless Architectures (tale introduzione non è affatto esaustiva; per una trattazione più approfondita consiglio gli ottimi articoli di Martin Fowler: https://martinfowler.com/articles/microservices.html e https://martinfowler.com/articles/serverless.html). Nella seconda parte viene descritta la soluzione Google – recentemente rilasciata in versione Beta – che consente di implementare un’architettura a microservizi in modalità fully-managed sul Cloud: Google Cloud Functions. Infine, sarà proposto un caso d’uso realistico per osservare l’approccio in azione.

Microservizi

L’architettura Software a microservizi è un approccio per lo sviluppo di applicazioni composte da un insieme di piccoli servizi (micro-servizi, appunto), ognuno eseguito in un proprio processo e comunicanti tra loro attraverso protocolli leggeri (come delle HTTP REST API o i più recenti endpoint gRPC). Tale architettura si contrappone alla più tradizionale architettura monolitica, per la quale le componenti dell’applicazione, sebbene logicamente separate, fanno parte di un singolo artefatto e vengono generalmente eseguite nel contesto di un singolo processo (pensiamo ad esempio alla classica applicazione Web Java EE, impacchettata dentro un singolo oggetto WAR ed eseguita all’interno di un Application Server come singola entità).

Serverless Architectures on Google Cloud Platform by Lorenzo Ridi

L’architettura a microservizi sta riscuotendo un crescente successo in una varietà di contesti; in buona misura, questo è dovuto alla sempre maggiore possibilità di far scalare orizzontalmente la propria infrastruttura – ovvero, implementare cluster costituiti da nodi di computing relativamente poco costosi. Di seguito riportiamo i principali vantaggi introdotti dai microservices:

Governance distribuita

In una architettura monolitica, tutti i team di sviluppo (ad esempio, il team di Frontend e quello di Backend) lavorano sullo stesso codebase; ciò implica che tutti gli sviluppatori debbano avere un’intera copia di tutta l’applicazione. A sua volta ciò rende complicato implementare una corretta separation of concerns, e a volte può rappresentare un vero e proprio rischio per la sicurezza e l’integrità del progetto.

Con l’architettura a microservizi, ogni team lavora sulla propria componente in totale autonomia. Posto che esista una vera e propria governance dei team di lavoro (condizione necessaria affinché il tutto non si trasformi in una infernale confusione), ciò che è obbligatorio per ogni gruppo è soltanto garantire una interfaccia consistente, una sorta di “contratto” nei confronti degli altri servizi.

Serverless Architectures on Google Cloud Platform by Lorenzo Ridi

Paradigmi di comunicazione

In una applicazione monolitica ogni componente comunica generalmente con le altre attraverso chiamate a metodi o funzioni; questo può generare un forte accoppiamento (coupling) tra le componenti, con l’immediata conseguenza di rendere difficoltosa (se non impossibile) l’evoluzione di una parte dell’applicazione senza doverne modificare altre.

I microservizi, di contro, comunicano attraverso paradigmi e protocolli che rendono semplice la definizione di una interfaccia formalizzabile e non ambigua (es. una API REST). Il disaccoppiamento tra le componenti può essere ulteriormente rafforzato utilizzando tecnologie di messaggistica asincrona, come nelle architetture publisher/subscriber.

Serverless Architectures on Google Cloud Platform by Lorenzo Ridi

Data management distribuito

Così come per la governance dei team di sviluppo, l’adozione dei microservices porta alla conquista di una relativa indipendenza nella scelta della base dati. È vero infatti che “no size fits all”: alcune componenti possono aver bisogno di datastore di un tipo o di un altro a seconda dei loro requisiti di utilizzo. Da questo punto di vista le variabili sono moltissime: DB relazionali/NoSQL, strong/eventual consistency, in-memory/persistent storage.

Sento già mormorare dal fondo: “non è questa è la strada verso un disastro fatto di duplicazioni e inconsistenze?”. Concordo pienamente: ecco perché anche in questo caso è necessaria una appropriata data governance, al fine di minimizzare la replica non necessaria dei dati (e la conseguente necessità di mantenerli allineati) all’interno della propria organizzazione.

Serverless Architectures on Google Cloud Platform by Lorenzo Ridi

Deploy e scalabilità

Eccoci giunti a quello che probabilmente è il maggior vantaggio dell’approccio a microservizi. Quando un’applicazione monolitica deve essere resa scalabile, l’unico approccio possibile è replicarla interamente su più nodi e far sì che questi siano raggiunti in maniera equa dal traffico, ad esempio attraverso un load balancer. Di contro i microservizi sono, per natura, replicabili in maniera indipendente: ciò significa che lo stesso cluster può configurarsi per far scalare un servizio piuttosto che un altro a seconda del traffico ricevuto (non è inusuale il caso in cui alcune procedure siano più utilizzate di altre in certi momenti della giornata o della settimana). Ciò comporta una maggiore elasticità e un miglior utilizzo delle risorse, con una conseguente riduzione del Total Cost of Ownership dell’intera infrastruttura.

Serverless Architectures on Google Cloud Platform by Lorenzo Ridi

Cos’è un microservizio?

Abbiamo enunciato fin qui i vantaggi dell’architettura a microservizi. Una domanda che può ragionevolmente sorgere a questo punto è “ok, ma che cos’è un microservizio?”, o in altre parole: quando si parla di microservizi, qual è l’unità di misura? quanto deve essere piccolo un microservizio? è un singolo metodo? una classe? un gruppo più o meno coeso di funzionalità?

Serverless Architectures on Google Cloud Platform by Lorenzo Ridi

Come ci si può aspettare, non c’è una risposta univoca. Anzi, la risposta a questa domanda determina il tipo di architettura a cui si fa riferimento:

Se l’unità di misura è l’intera applicazione, ci si riporta al caso iniziale dell’architettura monolitica.

Se l’unità di misura è un gruppo di funzionalità sufficientemente coeso da poter essere rilasciato indipendentemente, l’architettura è una buona candidata per essere modularizzata attraverso Containers, e quindi gestita attraverso una delle relative tecnologie di orchestrazione: Docker Swarm, Mesos, Kubernetes, per citarne alcune.

Se l’unità di misura è una singola funzione o metodo, si entra nel regno delle Serverless Architectures. Questo approccio “estremo”, in cui l’applicazione viene disgregata nelle sue componenti minime, si rivela spesso vincente in termini funzionali ed economici, tuttavia rimane un territorio che solo recentemente ha cominciato ad essere esplorato; questo principalmente per via delle notevoli difficoltà tecnologiche nel raggiungere un livello di granularità così elevato mantenendo performance consistenti. Ad oggi infatti, solo i principali player Cloud offrono delle reali soluzioni serverless, proprio in virtù della loro disponibilità di risorse computazionali e del grado tecnologicamente avanzato dei loro meccanismi di process scheduling.

https://twitter.com/ggreer/status/839171195920498688 (da includere come Tweet)

Serverless Architectures on the Cloud

Quando si parla di Cloud, siamo abituati a due termini spesso contrapposti: Infrastructure as a Service (IaaS) e Platform as a Service (PaaS). Grazie all’avvento delle Architetture Serverless, a questa coppia si aggiungono altri due elementi:

  • Backend as a Service (BaaS): in questa famiglia cadono tutti quei servizi che offrono funzionalità più o meno “standard” in modalità managed (cioè senza che il programmatore debba installarle, implementarle o manutenerle). Alcuni esempi: Database as a Service (Google Cloud SQL, Firebase), meccanismi di API Management e Autenticazione (Google Cloud Endpoints)
  • Functions as a Service (FaaS): in questa categoria rientrano tutti quei servizi che consentono di eseguire funzioni scritte dall’utente in risposta a specifici eventi, in modalità managed.

Serverless Architectures on Google Cloud Platform by Lorenzo Ridi

Google Cloud Platform è per sua natura orientata verso l’offerta di servizi managed; come ci si può aspettare quindi, offre un gran numero di servizi BaaS adatti in una moltitudine di casi d’uso:

  • Storage e Database: Google Cloud Storage, Google Cloud SQL, Google Cloud Bigtable
  • Data warehouse: Google Big Query
  • Middleware: Google Cloud Pub/Sub
  • Monitoring: Google Stackdriver

Più recentemente, Google ha rilasciato un servizio FaaS, attualmente in versione Beta, che complementa l’offerta serverless di Cloud Platform: Google Cloud Functions.

Serverless Architectures on Google Cloud Platform by Lorenzo Ridi

Di seguito riportiamo alcune delle caratteristiche più interessanti del nuovo servizio:

  • Serverless: le funzioni rilasciate vengono eseguite sulla performante infrastruttura Google e su un runtime completamente managed. Non c’è necessità di allocare e configurare alcun server.
  • Runtime Node.js: il runtime di esecuzione si basa sulla versione LTS più recente di Node.js e sul performante Javascript Engine V8, realizzato interamente da Google. Alcune librerie di uso comune (come ImageMagick) sono preinstallate e utilizzabili da tutte le funzioni. La sintassi delle funzioni, inoltre, è compatibile con i costrutti standard della programmazione Node.js (ad esempio, l’utilizzo di promises o l’utilizzo del file package.json per la dichiarazione delle dipendenze).
  • Sistema Operativo Debian: il runtime Node.js è installato su una versione vanilla di Debian 8 “Jessie”. Questo permette di avere pieno controllo sulla eventuale compilazione di librerie native (se necessarie).
  • Versioning: Google Cloud Functions è integrato con Google Cloud Repositories, servizio che consente di versionare il codice sorgente all’interno di un repository Git ospitato sulla piattaforma Google, oppure di utilizzare un repository esterno su GitHub o Bitbucket.
  • Just-in-time npm install: L’esecuzione del comando npm install viene effettuata sui server Google in fase di deploy. Ciò snellisce la quantità di file che deve essere gestita dallo sviluppatore e trasferita sulla rete per effettuare il deploy (di fatto si limita al codice sorgente e ad un eventuale file di dipendenze), ma soprattutto garantisce la totale consistenza del sistema di installazione con quello di esecuzione della funzione; ciò è particolarmente utile nel caso in cui alcune librerie native richiedano una fase di compilazione platform-dependent.
  • Emulazione locale e debugging: È disponibile un emulatore locale che consente di testare la propria funzione prima di effettuarne il deploy. L’emulatore è compatibile con i più diffusi IDE per il debugging di codice Node.js (Chrome Devtools, Visual Studio Code o Webstorm)
  • Monitoring: Google Cloud Functions è nativamente integrato con Google Stackdriver. Ciò significa poter fare affidamento su una piattaforma di logging, monitoring, alerting e error reporting disponibile out-of-the-box.

Cosa è possibile implementare con Google Cloud Functions? Per rispondere a questa domanda è bene sapere che esistono due distinte tipologie di funzioni: Background Functions e HTTP Functions. Vediamole nel dettaglio:

  • Background Functions: sono funzioni che rispondono ad eventi generati da altre componenti della piattaforma:

Google Cloud Storage: la funzione viene invocata ogni volta che un oggetto viene aggiunto/modificato/rimosso sul servizio di storage. Tipico esempio di una funzione di questo tipo è una procedura di processing automatico di contenuti: nella figura è rappresentata la transcodifica di video.

Serverless Architectures on Google Cloud Platform by Lorenzo Ridi

 

    • Google Cloud Pub/Sub: la funzione viene invocata ogni volta che un messaggio viene inviato su uno specifico topic del servizio di middleware.
    • Firebase: la funzione viene invocata ogni volta che un evento viene recepito dalla piattaforma; poiché Firebase aggrega un real-time database, un sistema di profilazione e un motore di analytics (vedi qui per ulteriori dettagli), gli eventi sono estremamente vari, spaziando dall’aggiunta di una specifica chiave nel database alla modifica di un utente – consiglio di fare riferimento alla documentazione ufficiale per ulteriori informazioni.
  • HTTP Functions: sono funzioni che vengono eseguite quando invocate attraverso un endpoint:
    • Browser: le funzioni possono essere esplicitamente invocate dagli utenti attraverso un client HTTP (come un Browser Web), grazie al completo supporto dei protocolli HTTP e HTTPS; alla creazione di una funzione HTTP il sistema produce un endpoint raggiungibile attraverso un fully qualified domain, e nel caso di un endpoint sicuro (HTTPS) genera automaticamente un certificato SSL/TSL.
    • Webhooks: le Cloud Functions possono essere utilizzate per integrare servizi di ogni genere attraverso interazioni server-to-server (recentemente battezzate webhooks). Nella figura è riportato un esempio che prevede che per ogni evento di push su un repository GitHub venga generato un messaggio Slack su uno specifico canale:

Serverless Architectures on Google Cloud Platform by Lorenzo Ridi

GitHub e Slack sono solo due dei servizi che supportano webhooks, ma ovviamente è possibile trovare innumerevoli altri esempi di integrazione, dalla Office Automation all’Internet of Things – sky is the limit! :)

Serverless Architectures on Google Cloud Platform by Lorenzo Ridi

Selfie-o-matic! ovvero: le Google Cloud Functions in azione

Finora abbiamo dato un’occhiata al paradigma microservices e serverless, abbiamo descritto la soluzione fornita dalle Google Cloud Functions e abbiamo esaminato alcuni ipotetici casi d’uso. Adesso è l’ora di vedere le Cloud Functions in azione!

Per mostrare la facilità con cui possiamo integrare i diversi servizi di Cloud Platform grazie a Google Cloud Functions, decidiamo di utilizzare per il nostro esempio anche alcune delle API di Machine Learning disponibili nella piattaforma (“Serverless Machine Learning”: buzzword nirvana!).

Il nostro obiettivo è realizzare una app (Selfie-o-matic™) che permetta di scattare un selfie con il proprio cellulare e di scrivere un commento testuale. Il sistema, sulla base della nostra espressione e del sentiment del commento, apporterà alcune modifiche all’immagine per rendere ancora più evidente il nostro mood. Seguono due esempi illustrativi:

Serverless Architectures on Google Cloud Platform by Lorenzo Ridi

Serverless Architectures on Google Cloud Platform by Lorenzo Ridi

L’applicazione è totalmente web-based e raggiungibile attraverso un qualsiasi browser mobile (potete testarla anche voi qui). Di seguito uno schema di funzionamento:

Serverless Architectures on Google Cloud Platform by Lorenzo Ridi

  1. Il dispositivo recupera l’interfaccia (HTML + CSS + JS) da un bucket Google Cloud Storage pubblico.
  2. L’utente inserisce i dati richiesti e invoca una HTTP Cloud Function inviando una richiesta multipart (immagine + metadati)
  3. La funzione invia l’immagine alle Cloud Vision API per recuperare i volti presenti nella foto e identificarne il mood (gioioso/neutrale/arrabbiato)
  4. Contestualmente, la funzione invia il testo del commento inserito dall’utente alle Natural Language API, le quali, grazie ad algoritmi di sentiment analysis, determinano se si tratti di un commento positivo o negativo.
  5. Sulla base dei dati ricevuti dalle API, la funzione effettua qualche elaborazione sull’immagine (modifica del contrasto / aggiunta di elementi) e restituisce l’immagine al client. L’immagine modificata viene visualizzata sull’interfaccia dell’utente.

Se volete dare un’occhiata più da vicino, l’intero codebase (client + server) è disponibile qui.