Architetture a microservizi Architettura dei Sistemi Software 27 maggio 2016 Paolo D’Incau [email protected] www.xpeppers.com 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. Bounded Context Utenti Ordini Magazzino 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 interservizio, 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 Utenti Validazione API Marca Remote Store Input Estrazione Amministrazione Utente? Utenti Validazione Utente? API Marca Remote Store Input 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 Utente? Validazione Utenti L. B. API Utente? Utenti Marca Remote Store Input Troppe Richieste: caching Utente? Utenti C A C H E Validazione API Utente? Marca Remote Store Input 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 Utenti esegui Marca API Remote Store Input 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 Command Bus Command Handler Event Bus User Inteface Event Handler Queries Domain Storage Domain Storage 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 Kibana Logs Logstash ES 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. Letture consigliate Recommended reading http://amzn.to/1UJhPkz http://bit.ly/1sNK07n