Architetture a microservizi
Architettura dei Sistemi Software
27 maggio 2016
Monolith
In un architettura classica (monolitica), tutte le funzionalità del sistema sono racchiuse in una unica entità che giorno dopo giorno tende a diventare sempre più grande e complessa da mantenere.
Problematiche
Un sistema monolitico tende ad essere fortemente accoppiato (e.g. linguaggio, features, deploy).
Con il tempo diventa difficile capire dove una determinata funzionalità è implementata e spesso si introducono regressioni o duplicazioni quando si aggiunge qualcosa di nuovo.
Microservizi
In un’architettura basata sui microservizi il sistema è suddivido in svariate componenti di dimensioni ridotte che hanno un ciclo di sviluppo e rilascio indipendente.
Definizione
Un microservizio dovrebbe contenere tutto quello che serve a garantire una funzionalità autocontenuta del sistema (bounded context).
Ogni microservizio può comunicare con altri microservizi al fine di ottenere informazioni necessarie al proprio funzionamento.
Bounded Context
Rappresenta una specifica responsabilità definita da dei confini ben definiti. Ogni context ha delle interfacce di comunicazione con l’esterno.
Il concetto è introdotto da Eric Evans in “Domain Driven Design”, libro che propone un paradigma differente di affrontare lo sviluppo di un determinato software.
Magazzino
Ordini
Utenti
Bounded Context
Massimizzare Cohesion
Tutto ciò che è pertinente ad una determinata funzionalità deve stare “vicino” (come affermato nel Single Responsibility Principle).
In questo modo riusciamo a trovare in modo facile il codice relativo, ed evitiamo di avere logica simile o correlata disseminata nella codebase.
Minimizzare Coupling
Quando il codice è accoppiato, un cambiamento in un determinato punto causa dei forzati cambiamenti in altri punti non necessariamente necessari.
Riducendo il coupling si riesce a garantire che il deploy di una componente non necessiti quello di un’altra.
Benefici
Andando ad adottare un approccio basato sui microservizi possiamo ottenere:
• eterogeneità tecnologica • resilienza • scalabilità • facilità/velocità di deploy • migliore organizzazione del team
Eterogeneità tecnologica
Un approccio a microservizi ci permette di sperimentare e di usare lo strumento giusto per risolvere il problema che abbiamo davanti.
Non esiste la paura di provare una nuova tecnologia perché una eventuale riscrittura sarebbe contingentata a poche righe di codice.
Resilienza
E’ un dato di fatto che il software fallisce. Suddividendo la nostra architettura in microservizi possiamo garantire che il fallimento di una singola componente non pregiudichi il funzionamento dell’intero sistema.
• bulkhead • circuit breaker • distribuzione
Scalabilità
A differenza di un’architettura monolitica, un’architettura a microservizi permette di far scalare solo la parte dell’applicativo che ne ha necessità.
Utilizzando strumenti come AWS è possibile scalare i nostri servizi in base a delle metriche di utilizzo del sistema.
Facilità/Velocità di deploy
In una architettura monolitica, il più piccolo cambiamento alla codebase comporta il dover rilasciare tutta la codebase.
L’approccio a microservizi permette di poter rilasciare invece solo il servizio che è variato senza andare ad impattare sul resto dell’applicativo.
Migliore organizzazione del team
Ogni microservizio è a carico di un team crossfunzionale (frontend, backend, db, operation) che lavora per ottenere una determinata funzionalità.
I team hanno libertà decisionale sullo stack e si assumono la responsabilità end-to-end.
Idealmente vale la regola del “Two-Pizza Team”.
Svantaggi
Non dobbiamo pensare che i microservizi siano una soluzione “silver bullet”.
Problematiche:
• Necessita una certa maturità del team • Aumento della complessità • Problematiche relative alla gestione dei sistemi distribuiti
Complessità
All’aumentare del disaccoppiamento aumenta la complessità.
Maggior numero di SCM, conoscenza divisa su più team, diverse tecnologie e modi di rilascio.
Distribuzione
Si deve riuscire a garantire la comunicazione inter-servizio, o almeno creare dei meccanismi che minimizzino eventuali problemi.
Non è sempre facile capire cosa è andato storto e soprattutto dove (e.g. monitoring, logging).
Tenere in considerazione il CAP theorem.
Esempi vincenti
Realtà che hanno abbracciato un approccio a microservizi e che ne sponsorizzano l'applicazione:
• Netflix • Spotify (http://bit.ly/1mOSHLk) • Zalando (http://bit.ly/20XSW9b)
Esempio reale: applicazione monolitica
• caricamento documenti testuali • diversi tipi di documenti • tramite diversi endpoint (e.g. queue, web) • validazione del contenuto
• REST api • permettono all’utente di eseguire comandi sul
sistema • consumate da un’interfaccia web (AngularJS) • consumate da applicazioni mobile
Esempio reale: applicazione monolitica
• amministrazione • creazione utenti • profilazione utenti • interfaccia web (AngularJS)
• notifica verso servizi esterni • store remoto • applicazione di marcatura digitale
Architettura Monolith
UtentiValidazione
Remote Store
InputAPI
Marca
Estrazione Amministrazione
UtentiValidazione
Remote Store
InputAPI
Marca
Utente?
Utente?
Troppe Richieste?
Se ad ogni documento caricato o in fase di validazione dello stesso andiamo a richiedere l’utente che sta caricando rischiamo di stressate troppo il servizio estratto.
Soluzioni:
• Scalare orizzontalmente • Applicare politiche di caching
Troppe Richieste: scalare
Utenti
Validazione
Remote Store
InputAPI
MarcaUtente?
Utente?
Utenti
Utenti
L. B.
Troppe Richieste: caching
Validazione
Remote Store
InputAPI
MarcaUtente?
Utente?
Utenti
C A C H E
Scalare vs Caching
Scalare è molto semplice quando abbiamo una infrastruttura cloud (possiamo scalare in modo programmato a seconda del carico misurato).
Il meccanismo di caching è molto performante perché evita chiamate inutili a servizi o database.
Estrazione Marcatura
Validazione
Remote Store
InputAPI
Marca
Utenti
esegui
E se servizio di marca non risponde?
Se il servizio di marca non è attivo per un qualsiasi disservizio potremmo avere migliaia di richieste che falliscono solo dopo un determinato timeout, andando a generare una grandissima latenza tra i nostri vari servizi.
Soluzione:
• Rendere il servizio di marca asincrono
Asincronia ed Eventual Consistency
Nel nostro caso, il processo di marcatura può essere eseguito in modo asincrono e adottare politiche di eventual consistency.
Una possibile soluzioni consiste nel mettere il dato in una coda e fornire una dashboard che permetta di risottomettere il documento alla marcatura.
Le richieste alle API cosa fanno?
Spesso ci si rende conto nella maggior parte delle nostre applicazioni, gli utenti vanno a fare due tipi di operazioni:
• Modifiche allo stato del sistema (poche) • Letture sullo stato del sistema (molte)
CQRS
Command-Query Responsibility Segregation (CQRS) è un pattern che ci permette di separare logicamente la nostra applicazione in:
• comandi che vengono validati e applicati ai nostri modelli e che modificano il loro stato
• query che vanno a interrogare delle proiezioni del sistema ottimizzate alla visualizzazione
CQRS
User Inteface
Domain Storage
Command Handler
Command Bus
Event Bus
Event Handler
Domain Storage
Queries
CQRS: Benefici
Questo separazione di responsabilità ci garantisce diversi vantaggi:
• comandi e query posso risiedere su servizi/macchine diverse —> scalabilità
• possiamo usare diverse tecnologie (e.g. database per le diverse parti)
• abbandonare design “database driven”
Logging e Monitoring
Qualsiasi applicazione software necessita di meccanismi di logging per storicizzare cosa è accaduto nel sistema.
Allo stesso modo è importante avere dei meccanismi di monitoring che verifichino che le varie componenti della nostra architettura funzionino in modo corretto.
Logging e Monitoring
Passare da un’architettura monolitica a un’architettura a microservizi significa andare ad aggiungere uno strato di complessità.
Servono strumenti che ci permettano di aggregare ed analizzare i dati provenienti dalle diverse fonti:
• ELK (Elesticsearch + Logstash + Kibana) • SENSU
Logging e Monitoring
Logstash
Kibana
ES
Logs
Logs
Esempio: dashboard
Logging e Monitoring
Va sempre tenuto in considerazione che i log e le metriche di monitoraggio non comunicano solo informazioni utili agli sviluppatori.
Affrontare queste tematiche usando un’ottica di collaborazione ci permette di estrarre dati utili in ottica di business.
Possibili Metriche
Ad esempio nel nostro applicativo potremmo essere interessati a:
• numero di documenti caricati • distribuzione del carico nelle ore/giornate • errori più comuni di validazione • documenti marcati con errore • etc, etc…
Conclusioni I
Utilizzare i microservizi ci permette di andare a costruire il software e il modo in cui lavorano i nostri team attorno a concetti di business e non attorno a tecnologie.
Possiamo inoltre lavorare il software in modo che sia rilasciabile in modo indipendente, riducendo dunque il “time to market”.
Conclusioni II
Un’architettura a microservizi, sebbene sia più complessa da gestire rispetto ad un monolito permette di isolare in modo migliore i possibili fallimenti e di scalare in modo più semplice.
Si devono introdurre dei meccanismi di logging e monitoring più sofisticati al fine di garantire che il sistema si comporti nel modo corretto.
Recommended reading
http://amzn.to/1UJhPkz
Letture consigliate
http://bit.ly/1sNK07n