În ultimii șase ani, împreună cu o echipă foarte bună de programatori, am lucrat la realizarea unei noi aplicaţii Enterprise. La momentul începerii proiectului, tehnologia disponibilă era destul de prematură, astfel încât am avut la dispoziţie doar variante alpha şi beta a mai multor librării (MVC5, WebAPI2, Entity Framework 6, KnockoutJS, jQuery, kendoUI). Şase ani mai târziu, tehnologia s-a maturizat. Acum avem pe piaţă o aplicaţie Enterprise, care a fost implementată cu succes la mai mulți clienţi din diferite părţi ale lumii. Recent, clientul care vinde acest produs a făcut o pivotare a modelului de business spre SaaS.
În acest articol nu voi încerca să expun avantajele şi dezavantajele unui monolit, respectiv ale unei arhitecturi bazate pe microservicii, ci mai degrabă doresc să prezint rezultatele cercetării făcute de noi pentru trecerea de la o arhitectură monolit la una bazată pe microservicii, având în vedere un model de business SaaS, cu deployment în cloud (AWS, mai exact).
Aplicaţia are o arhitectură pe mai multe nivele. Acestea sunt DataLayer, BusinessLayer şi ServiceLayer, iar deasupra ServiceLayer-ului este o aplicaţie HTML5, construită ca o colecţie de SPA-uri (Single Page Application).
Toate nivelele aplicaţiei care sunt sub layerul de HTML5 şi deasupra RDB-ului rulează în acelaşi proces, ceea ce face ca aplicaţia să ruleze ca un monolit.
Schema este simplă, însă pentru a înţelege mai bine magnitudinea proiectului, găsiţi în continuare mai multe detalii despre conţinut:
DataLayerul are în jur de 700-800 de entități create cu entity framework (database first). Acelaşi număr de tabele/views se aplică şi în cazul bazei de date, ţinând cont că DataLayerul este mapat la TVF (Table Value Functions).
BusinessLayerul are în jur de 250 de mii de linii de cod ce pot fi executate în contextul a mai multor aplicaţii de tip client (REST Services şi/sau SOAP Services), deseori chiar în procese diferite.
ServiceLayerul este un proxy către BusinessLayer ce expune servicii REST.
Statisticile de mai sus au fost extrase din ultima rulare SonarQube, din pipeline-ul de Continuous Integration al proiectului.
Avem o aplicaţie care pune la dispoziţie o varietate de functionalităţi ce pot fi folosite în integrare cu terţe aplicaţii sau chiar pentru aplicaţii nou create.
Dacă dorim să expunem funcţionalităţile într-un business model SaaS, atunci ar trebui să luăm în considerare diverse aspecte tehnice, cum ar fi: "Costs of deployment", "Data Governance/Data segregation", "Multi tenancy", "Privacy", "Compliance", "Security", "Availability" and "Technology".
Termenul de microservicii este deja un buzz-word bine cunoscut, iar în domeniul nostru este un termen în jurul căruia s-au pornit multe dezbateri. Fără să intru într-o asemenea dezbatere şi fără să pornesc discuţia definirii termenului de microservicii, iată câteva provocări cu care ne-am întâlnit concret şi pe care am dorit să le rezolvăm folosind acest concept:
La fiecare start-up al unui monolit, acesta consumă multe resurse, chiar dacă procesul e latent. În cazul în care am vrea să scalăm o anumită capabilitate a monolitului, trebuie să scalăm de fapt, tot monolitul, cu toate funcţionalităţile sale, ceea ce crește preţul deploymentului pentru clienţi.
Restricţii privind mediul de deployment în care se rulează aplicaţiile. În cazul nostru, folosind tehnologii .NET, suntem mai mult sau mai puţin dependenţi de Windows OS.
Mărimea aplicaţiei poate încetini procesul de pornire (Cold Start-up).
La fiecare versiune la care facem release, e nevoie să facem redeploy la tot monolitul, ceea ce implică mai multe riscuri.
Complexitatea codului şi a funcţionalităţii.
Un singur technology stack de development. Vrem să luăm în considerare chiar şi folosirea altor tehnologii care sunt foarte potrivite pentru implementarea anumitor capabilităţi.
Aşadar, cum trecem de la o arhitectură monolit la o abordare bazată pe microservicii?
Încearcă să te uiţi la monolitul la care lucrezi şi gândeşte-te la ce capabilităţi oferă. Odată ce le vei înţelege în profunzime, ia în considerare extragerea codului ce face referire la una dintre aceste capabilităţi sau chiar consideră rescrierea acesteia de la zero. Dacă ai în perspectivă rescrierea, atunci ai opţiunea de a alege tehnologia care se potriveşte cel mai bine cu acea capabilitate specifică.
Porneşte de la separarea capabilităţilor simple şi separate deja; astfel poţi să exersezi acest proces pentru a-l aplica şi pe capabilităţile mai complexe.
Capabilitatea de autentificare este un exemplu de capabilitate marginală, ceea ce o face, în teorie, mai uşor de separat.
Extrage "macroservicii" în prima instanţă. Este mai important să extragi o capabilitate specifică ce aduce valoare produsului decât să extragi doar cod. Atenția trebuie orientată către capabilităţi, nu pe cod; codul e doar un tool care produce capabilităţi.
De exemplu, în procesul de decuplare a capabilităţilor unui sistem de ECommerce, programatorii ar putea porni de la extragerea unui singur serviciu de tip "buy", în care să înglobeze atât conţinutul coşului de cumpărături, cât şi capabilitatea de cumpărare ("check-out"). Acest serviciu poate să fie considerat "macroserviciu" în ideea în care din capabilitatea de mai sus se poate extrage ulterior o capabilitate de "check-out". Aceşti paşi sunt benefici procesului de a sparge un monolit în bucăţi mai mici.
Când separi capabilităţile pe "slice"-uri, încearcă să o faci vertical. Dacă e posibil, încearcă să atingi toate layerurile monolitului.
De exemplu:
Figura 2 - Arhitectura multinivel împreună cu verticalele tehnice
În imaginea de mai sus, pe partea dreaptă, se observă cum toate nivelele au acum un "slice" vertical, bazat pe capabilităţi specifice (reprezentate în diferite culori: fiecare culoare reprezintă o capabilitate), ce pot fi transformate în noi aplicaţii, rezultând noi microservicii. Fiecare capabilitate poate avea amprente diferite pe fiecare nivel. De aceea, efortul de a extrage o anume capabilitate diferă în funcţie de impactul pe care îl are asupra monolitului.
Paşii de urmat pentru separarea unei noi capabilităţi:
Decuplează noul serviciu.
Redirecţionează toţi clienţii spre noul serviciu.
Într-o arhitectură de tip monolit, când construieşti o nouă capabilitate, tehnologia pe care o vei folosi este în general constrânsă de tehnologia în care a fost scris monolitul. De exemplu, dacă construieşti o nouă capabilitate într-un monolit scris în .NET, va trebui să te limitezi la .NET; acelaşi lucru se aplică la Java sau alte tehnologii.
Într-un mediu bazat pe microservicii, atunci când construieşti o nouă capabilitate, ai puterea şi flexibilitatea să alegi tehnologia care se potriveşte capabilităţii ce urmează să o creezi.
Figura 3 - Arhitectura multinivel împreună cu verticalele tehnice și funcționale
Chiar dacă am separat trei capabilităţi din monolit, drumul până la un ecosistem bazat doar pe microservicii continuă. Faptul că ai monoliţi mai mici sau "macroservicii" nu înseamnă că drumul e greşit; orice "slice" reuşeşti să scoţi din monolit, e un pas în faţă.
Însă, să nu uităm că, pentru a putea vinde produsul la care lucrăm în continuare, trebuie să livrăm noi funcţionalităţi pe piaţă în timp record. Totdeauna trebuie să încercăm să punem în balanţă efortul de a livra rapid cu timpul investit în livrarea capabilităţii ca un microserviciu.
Înainte de a separa monolitul în bucăţi mai mici - microservicii, în cazul în care ştii că urmează să faci deployment pe o platformă cloud, e indicat să investighezi ce servicii sunt deja disponibile pe acea platformă pe care vei lansa produsul sau capabilitatea implementată.
Acest lucru este necesar deoarece platformele cloud pot avea servicii gata de folosit şi nu mai este necesar să le construieşti tu.
Desigur că trebuie să fii atent atunci când alegi să foloseşti un serviciu al unei platforme și vei deveni "tightly-coupled" cu acea platformă. Dacă planifici să suporţi diverse platforme could (Azure/AWS/Alibaba), va trebui să iei în considerare construcţia unui nivel de abstractizare deasupra serviciilor cloud pe care plănuieşti să le foloseşti.
Mai jos, este un exemplu de deployment a trei noi capabilităţi (reprezentate în diferite culori - în secțiunea de private subnet) ce au fost extrase din monolit, împreună cu aplicaţia monolit.
Figura 4 - Scenariu de deployment AWS multiregiune
Capabilităţile pot rula fie în instanţe de EC2, fie Serverless ca funcţii lambda. În diagrama de mai sus, acestea sunt deployed ca funcţii lambda.
Iată câteva dintre serviciile AWS pe care considerăm să le folosim:
Cloudfront
Cache pentru serviciul S3 si EFS.
Gateway API
Folosit să expună către internet funcţionalităţile ecosistemului.
Un serviciu centralizat care poate să se ocupe de autentificare, autorizare, logare, throttling, etc. .
ELB ..
Load balancer peste instanţele de EC2
EC2
VM pentru găzduirea monolitului.
SQS
SNS
RDS
Cu posibilitatea de a avea o instanţă de tip "Stand-By".
Elastic search
DynamoDB
S3
Să presupunem că avem o colecţie de microservicii care ar trebui executate într-o ordine specifică şi configurabilă, cu un anumit nivel de atomicitate şi cu un nivel ridicat de interacţiune între acestea.
Pentru această orchestrare, am luat în considerare aplicaţii ce pot invoca servicii REST şi SOAP într-un mod configurabil. În cazul nostru, am decis să folosim Mendix.
Cu Mendix poţi crea ceea ce ei numesc "microflows". Un "microflow" îţi permite să exprimi schema logică a aplicaţiei SaaS. Acest microflow poate efectua acţiuni precum invocarea serviciilor REST şi SOAP într-un mod visual şi configurabil, fără a fi nevoie să scrii cod. Pe lângă invocări de servicii, ai posibilitatea să creezi entităţi şi view-uri pe care le poţi folosi pentru a face diferite transformări care sunt în general necesare pentru a îndeplini contractele microserviciilor. Trebuie să repet că funcţionalităţile de mai sus se pot face doar prin configurare.
Un exemplu de orchestrare prin configurare în Mendix se poate vedea în Figura 5.
Figura 5 - Exemplu de Mendix Microflow
Construirea unui produs SaaS pentru aplicaţii Enterprise este o încercare complexă. SaaS de abia a început să pătrundă în lumea Enterprise, în principal pentru aplicaţii noi. Adoptarea SaaS este condusă, evident, de business. Cu toate acestea, credem că în timp, mai multe aplicaţii "legacy" vor fi înlocuite de către SaaS, iar această tranziţie va fi accelerată odată ce produsele Enterprise SaaS se vor maturiza şi vor îndeplini cerinţele reale Enterprise.