În lumea dezvoltării software, este ușor să ai impresia că o aplicație modernă ar trebui să fie alcătuită din microservicii. Un serviciu separat pentru gestionarea utilizatorilor, unul pentru comenzi, altul pentru plăți și încă unul pentru notificări. Fiecare componentă poate fi dezvoltată independent, implementată separat și scalată atunci când are cu adevărat nevoie.
Pe diagramă, totul arată perfect. Fiecare componentă a sistemului are responsabilitățile sale, echipele pot lucra în paralel, iar defecțiunea unui serviciu nu ar trebui să oprească întreaga aplicație.
Problema începe atunci când diagrama devine realitate. În locul unei singure aplicații, echipa întreține zeci de servicii, mai multe mecanisme de comunicare, numeroase procese de implementare, jurnale suplimentare, sisteme de monitorizare, permisiuni și configurații. O operațiune de business simplă necesită comunicarea dintre mai multe componente. Testarea devine mai dificilă, iar găsirea cauzei unei erori presupune urmărirea întregului flux prin sistem.
Înseamnă asta că microserviciile sunt o alegere greșită? Nu. În condițiile potrivite, ele pot îmbunătăți semnificativ modul în care sunt dezvoltate produsele de mari dimensiuni. Totuși, ele nu reprezintă o etapă automată în maturizarea aplicației și nici o garanție a unei scalabilități mai bune.
O arhitectură bună nu este cea alcătuită din cel mai mare număr de servicii. Este cea care permite dezvoltarea produsului fără creșterea inutilă a costurilor, riscurilor și complexității.
De aceea, înainte de a lua o decizie, merită să înțelegem prin ce se deosebesc monolitul, monolitul modular și microserviciile, ce probleme rezolvă și ce noi responsabilități implică.
Ce este, de fapt, un monolit?
Un monolit este o aplicație ale cărei componente principale sunt dezvoltate și implementate ca o singură unitate. Poate conține numeroase module, poate gestiona procese de business diferite și poate avea o arhitectură internă complexă. Prin urmare, faptul că un sistem este monolitic nu înseamnă că întregul cod se află într-un singur fișier sau că aplicația este dezorganizată.
Un exemplu ar putea fi un sistem care gestionează vânzările, clienții, produsele, comenzile, plățile și raportarea. Toate aceste funcționalități pot face parte dintr-o singură aplicație, care folosește o bază de date comună și este implementată ca un singur produs.
O astfel de structură are avantaje concrete. În multe cazuri, este mai ușor de pornit, testat și implementat. Apelurile dintre module pot avea loc direct, în cadrul procesului aplicației, fără a fi nevoie să se trimită cereri prin rețea. Tranzacțiile care implică mai multe zone ale sistemului pot fi mai ușor de realizat dacă folosesc o bază de date comună.
Monolitul poate reduce și numărul componentelor de infrastructură care trebuie administrate. Nu este nevoie să configurezi procese separate de implementare, monitorizare și comunicare pentru fiecare funcționalitate minoră.
Desigur, monolitul are și limitări. Dacă de-a lungul anilor se adaugă funcționalități fără a controla dependențele, codul devine tot mai greu de înțeles. O modificare într-un loc poate cauza probleme în altul, iar implementarea unei funcționalități minore necesită testarea și publicarea întregii aplicații.
Totuși, aceasta nu este o caracteristică inevitabilă a monolitului. Adesea, problema nu ține de modul în care este implementată aplicația, ci de structura ei internă.
Nu toate monoliturile sunt la fel
În discuțiile despre arhitectură, toate aplicațiile monolitice sunt adesea puse în aceeași categorie. Însă există o diferență importantă între un sistem ale cărui module au limite clar definite și o aplicație în care fiecare componentă poate face referire oricum la celelalte.
În primul caz, avem o structură organizată. În al doilea, se poate ajunge la așa-numitul big ball of mud, adică un sistem cu limite neclare, numeroase dependențe și o logică greu de reconstituit.
Să ne imaginăm o aplicație de vânzări în care modulele pentru clienți, produse, comenzi și plăți au responsabilități proprii. Modulul de comenzi folosește o interfață specifică a modulului de clienți, nu fragmente alese la întâmplare din implementarea sa internă. Modificările dintr-o zonă nu ar trebui să necesite schimbări necontrolate în întregul sistem.
Dacă, în schimb, fiecare modul modifică direct datele celorlalte module, le folosește clasele interne și presupune că le cunoaște detaliile de funcționare, aplicația își pierde treptat structura.
Aici intervine monolitul modular.
Monolitul modular: un compromis demn de luat în considerare
Monolitul modular rămâne o singură aplicație care se implementează ca o unitate, însă interiorul său este împărțit în module clar definite. Fiecare dintre ele răspunde de un anumit domeniu de business, are limite clar stabilite și oferă celorlalte componente ale sistemului o modalitate controlată de colaborare.
În practică, aceasta poate însemna module responsabile de clienți, catalogul de produse, comenzi, facturare sau notificări. Fiecare are propria logică, iar accesul la mecanismele sale interne este limitat.
Cea mai importantă diferență este că limitele logice nu trebuie să fie și limite de infrastructură. Modulele pot fi bine separate în cod, chiar dacă aplicația continuă să fie rulată și implementată ca întreg.
Acest model ajută la organizarea responsabilităților, la reducerea dependențelor și la pregătirea sistemului pentru dezvoltarea ulterioară, fără a introduce toate costurile unei arhitecturi distribuite.
Monolitul modular poate fi o soluție deosebit de potrivită atunci când:
-
produsul este dezvoltat de o singură echipă sau de câteva echipe care colaborează strâns;
-
limitele de business ale sistemului sunt încă în curs de definire;
-
majoritatea funcționalităților folosesc aceleași date și trebuie să le păstreze coerența;
-
nu este nevoie de scalarea independentă a anumitor zone;
-
procesul actual de implementare funcționează suficient de bine;
-
compania dorește să păstreze posibilitatea de a separa ulterior anumite module.
Ultimul aspect este deosebit de important. Începerea cu un monolit modular nu înseamnă că se renunță la posibilitatea de a trece ulterior la microservicii. Dacă limitele modulelor sunt bine proiectate și nevoile de business se schimbă, o anumită zonă poate fi separată ulterior într-un serviciu distinct.
Totuși, aceasta nu este o migrare automată, care se face printr-un singur clic. Trebuie să iei în considerare stocarea datelor, dependențele, tranzacțiile, comunicarea și responsabilitatea pentru întreținerea noii componente. O modularitate bine gândită poate însă simplifica semnificativ acest proces.
Microserviciile: ce beneficii aduc cu adevărat?
Arhitectura bazată pe microservicii împarte aplicația în servicii mai mici, care realizează anumite funcții de business și pot fi dezvoltate și implementate independent. Serviciile comunică între ele prin interfețe definite, adesea prin API, mesaje sau evenimente.
Astfel, echipa poate modifica un serviciu fără a fi nevoie să publice întreaga aplicație. Componentele individuale pot folosi și tehnologii diferite sau pot fi scalate independent, dacă volumul lor de lucru și cerințele diferă într-adevăr.
De exemplu, o platformă de comerț electronic poate avea un serviciu separat pentru căutarea produselor, a cărui încărcare crește mult mai rapid decât cea a modulului de gestionare a conturilor utilizatorilor. Într-o arhitectură de microservicii, resursele serviciului de căutare pot fi suplimentate fără a scala toate celelalte componente ale sistemului.
O situație similară poate apărea atunci când o anumită zonă a produsului necesită implementări deosebit de frecvente, alta are cerințe distincte de disponibilitate, iar o alta este dezvoltată de o echipă independentă.
Prin urmare, microserviciile pot ajuta în câteva domenii concrete:
Implementări independente. O modificare a unui serviciu nu trebuie să implice lansarea tuturor celorlalte componente, atât timp cât contractele lor rămân compatibile.
Scalare independentă. Pot fi suplimentate resursele doar pentru serviciile care au nevoie de ele, în loc să fie scalat întregul sistem.
Responsabilități distincte ale echipelor. Echipele pot dezvolta independent anumite zone ale produsului, dacă limitele responsabilităților sunt proiectate corespunzător.
Izolarea unor probleme. Un serviciu proiectat corespunzător poate limita efectul anumitor defecțiuni asupra restului sistemului. Totuși, acest lucru nu se întâmplă automat. Dacă celelalte componente nu pot funcționa fără serviciul indisponibil, problema poate afecta în continuare o parte semnificativă a aplicației.
Flexibilitate tehnologică. Serviciile individuale pot fi adaptate la nevoi tehnice diferite, deși menținerea mai multor tehnologii mărește și costurile.
Merită remarcat că niciunul dintre aceste beneficii nu rezultă doar din numărul de servicii. Sunt necesare limite arhitecturale adecvate, automatizare, monitorizare, procese de testare și competențele echipei.
Costul separării sistemului: ce nu apare în diagramă?
Cea mai mare greșeală în planificarea microserviciilor este să le compari exclusiv pe baza structurii codului. Separarea aplicației schimbă nu doar modul în care este implementată, ci și modul în care este pornită, testată, implementată și depanată.
Într-un monolit, apelul unei funcții de către alta poate avea loc în cadrul aceluiași proces. Într-un sistem distribuit, colaborarea dintre servicii implică adesea comunicare prin rețea, care poate întârzia, eșua sau poate fi întreruptă într-un moment neașteptat.
Apar întrebări noi. Ce se întâmplă dacă serviciul A a efectuat o operațiune, dar serviciul B nu a primit răspunsul? Repetarea cererii nu va duce la executarea aceleiași operațiuni de două ori? Ce se întâmplă dacă o parte a procesului se încheie cu succes, iar celelalte componente nu înregistrează modificarea?
Acestea nu sunt probleme rare, pur teoretice. În sistemele care gestionează plăți, comenzi, rezervări sau stocuri, ele pot duce la discrepanțe reale în activitatea comercială.
La acestea se adaugă costurile de întreținere:
-
mai multe componente de monitorizat;
-
configurații și procese de implementare separate;
-
necesitatea urmăririi cererilor care trec prin mai multe servicii;
-
mecanisme suplimentare de comunicare și gestionare a erorilor;
-
teste de integrare și de contract;
-
gestionarea versiunilor API;
-
controlul accesului între componente;
-
mai multe puncte potențiale de defecțiune;
-
nevoia de competențe legate de sistemele distribuite.
Asta nu înseamnă că microserviciile sunt întotdeauna mai costisitoare pe întregul ciclu de viață. Într-un sistem suficient de mare, ele pot reduce costurile de coordonare și pot accelera dezvoltarea. Totuși, costul operațional inițial este real și trebuie luat în considerare în decizie.
Dacă o companie nu dispune de automatizarea implementărilor, de o monitorizare bună și de proceduri de răspuns la defecțiuni, separarea aplicației poate spori complexitatea mai repede decât aduce beneficii.
Când încep să aibă sens microserviciile?
Nu există un număr universal de utilizatori, solicitări sau programatori, după depășirea căruia trebuie să se treacă la microservicii. Decizia depinde de tipul produsului, de volumul său de lucru, de structura organizațională și de limitările reale ale arhitecturii actuale.
Totuși, pot fi indicate situațiile în care merită analizată serios separarea unei părți a sistemului.
1. Diferitele domenii au profiluri de încărcare semnificativ diferite.
Dacă o funcție necesită mult mai multă putere de calcul decât celelalte, scalarea independentă poate aduce beneficii măsurabile. Trebuie însă verificat dacă problema nu poate fi rezolvată prin metode mai simple, precum optimizarea interogărilor, memoria cache, o coadă de sarcini sau suplimentarea resurselor întregii aplicații.
2. Echipele trebuie să dezvolte independent domenii diferite.
Într-o organizație mare, baza de cod comună și un singur proces de lansare pot îngreuna activitatea mai multor echipe. Separarea responsabilităților poate îmbunătăți autonomia, cu condiția ca serviciile să aibă limite stabile și contracte clar definite.
3. Diferitele funcții au cerințe distincte de disponibilitate.
Unele domenii ale produsului pot necesita o rezistență deosebită la defecțiuni, în timp ce altele pot fi indisponibile temporar fără a opri procesele esențiale. Serviciile independente permit adaptarea strategiilor de scalare, implementare și recuperare la fiecare domeniu în parte.
4. Un modul necesită o tehnologie sau o metodă de procesare diferită.
Uneori, o anumită sarcină are cerințe justificate, a căror îndeplinire în aplicația principală ar fi ineficientă. În acest caz, separarea ei poate limita impactul acestor cerințe asupra restului produsului. Merită însă să ne amintim că un set suplimentar de tehnologii înseamnă și o responsabilitate suplimentară pentru întreținere.
5. Limitele domeniilor de business sunt deja bine definite.
Dacă se știe unde se încheie responsabilitatea unui domeniu și unde începe cea a altuia, este mai ușor să fie proiectate servicii independente. Când limitele sunt neclare și se schimbă frecvent, stabilirea lor prematură poate duce la dependențe costisitoare și la modificări continue ale interfețelor.
Principiul cel mai important este: separă un serviciu pentru că rezolvă o problemă concretă, nu pentru că încă o componentă din diagramă pare modernă.
Când este monolitul modular alegerea mai bună?
Monolitul modular este adesea alegerea câștigătoare atunci când compania are nevoie să-și organizeze aplicația, dar nu există încă o justificare pentru a o împărți în numeroase servicii independente.
Acest lucru se poate aplica unui produs nou, al cărui model de business este încă în dezvoltare. La început, este greu de prevăzut care funcții vor avea cea mai mare încărcare, care vor necesita implementări independente și unde se vor afla, în cele din urmă, limitele responsabilităților.
Același lucru este valabil pentru un sistem de business de dimensiuni medii, în care majoritatea operațiunilor se referă la date comune. Separarea logicii în mai multe servicii poate introduce probleme suplimentare legate de consistența datelor și de tranzacții, fără a oferi în schimb suficientă independență.
Un monolit modular merită luat în considerare și atunci când compania dispune de resurse operaționale limitate. Un singur proces de implementare și un model mai simplu de monitorizare pot fi o soluție mai rezonabilă decât întreținerea a zeci de servicii, fiecare necesitând propria configurare și propriile proceduri.
Asta nu înseamnă să lăsăm totul într-o singură aplicație fără un plan de viitor. Un monolit modular bine proiectat necesită disciplină: limite clare între module, acces controlat la date și limitarea dependențelor inutile.
Dacă aceste principii sunt respectate, compania poate beneficia de un model de implementare mai simplu, pregătind în același timp sistemul pentru eventuala separare a anumitor funcționalități în viitor.
Scalabilitatea nu înseamnă doar numărul de servere
Unul dintre cele mai frecvente argumente în favoarea microserviciilor este scalabilitatea. Merită însă să distingem două aspecte diferite: scalarea tehnică și capacitatea organizației de a dezvolta produsul.
Scalarea tehnică înseamnă capacitatea de a gestiona o încărcare mai mare. În multe cazuri, o aplicație monolitică poate fi rulată pe mai multe instanțe, folosind echilibrarea încărcării, cache, cozi de sarcini și o bază de date proiectată corespunzător. Un astfel de sistem poate gestiona un trafic considerabil fără a fi necesară împărțirea lui în zeci de servicii.
Desigur, nu orice monolit poate fi scalat în orice mod. Problemele pot proveni din stocarea stării în memoria procesului, utilizarea unei baze de date comune, operațiuni sincrone costisitoare sau limitările unei anumite componente. Mai întâi, însă, merită să stabilim ce anume reprezintă cu adevărat un blocaj.
Scalarea organizațională este o altă chestiune. Dacă la produs lucrează mai multe echipe care se blochează frecvent reciproc în timpul modificărilor și implementărilor, împărțirea responsabilităților poate aduce beneficii chiar și atunci când performanța sistemului este suficientă.
În practică, decizia ar trebui, așadar, să se bazeze pe măsurători. Are aplicația probleme de performanță? Durează implementările prea mult? Trebuie echipele să se pună de acord pentru fiecare modificare? Are o anumită funcționalitate nevoie de alte resurse? Afectează defecțiunile unui modul celelalte module?
Răspunsurile ajută la deosebirea unei probleme de arhitectură de una de infrastructură, de calitate a codului sau de organizare a muncii.
Migrarea la microservicii: de ce nu merită să separăm totul dintr-odată?
Dacă aplicația actuală începe într-adevăr să limiteze dezvoltarea produsului, migrarea la microservicii nu trebuie să însemne rescrierea întregului sistem dintr-o singură etapă.
O abordare mai sigură poate fi separarea treptată a unor domenii concrete. Mai întâi trebuie stabilit ce modul are o responsabilitate suficient de clară, ce date gestionează și cum interacționează cu celelalte părți ale aplicației.
Apoi se poate proiecta limita de comunicare, pregăti testele și transfera treptat responsabilitatea către noul serviciu. Astfel, ipotezele pot fi verificate într-un domeniu restrâns înainte ca firma să decidă să facă alte schimbări.
De exemplu, un sistem de vânzări poate include inițial un modul de generare a rapoartelor care solicită intens baza de date și încetinește operațiunile utilizatorilor. În loc să separăm întregul sistem, putem analiza mai întâi dacă raportarea poate fi mutată într-un proces sau serviciu separat, utilizând, eventual, date pregătite asincron.
O astfel de separare are sens dacă reduce cu adevărat încărcarea aplicației principale sau permite dezvoltarea și scalarea independentă a raportării. Dacă însă rapoartele sunt simple, iar încărcarea pe care o generează este redusă, serviciul suplimentar poate fi inutil.
Tocmai de aceea, migrarea ar trebui să pornească de la o problemă și de la rezultatul așteptat, nu de la presupunerea că toate modulele trebuie, în cele din urmă, să devină microservicii.
Merită să ne amintim și că separarea unui serviciu nu se încheie odată cu mutarea codului. Trebuie rezolvate aspecte legate de date, comunicare, autorizare, monitorizare, implementări, testare și responsabilitatea pentru defecțiuni. Abia atunci noua componentă devine cu adevărat un element independent al arhitecturii.
Cum luăm o decizie? O listă practică de întrebări
Înainte de a alege o arhitectură, merită să răspundem la câteva întrebări. Acestea nu înlocuiesc analiza tehnică, dar ajută la structurarea deciziei.
1. Ce problemă concretă vrem să rezolvăm? Este vorba despre performanță, durata implementărilor, dependențele dintre echipe, disponibilitate sau dificultatea întreținerii codului? Dacă nu putem identifica problema, este greu să evaluăm dacă microserviciile sunt soluția potrivită.
2. Problema este într-adevăr cauzată de arhitectură? O aplicație lentă poate avea nevoie de optimizarea interogărilor, iar implementările dificile pot fi cauzate de lipsa testelor și a automatizării. Schimbarea arhitecturii nu înlocuiește aceste măsuri.
3. Sunt limitele dintre domeniile de activitate suficient de clare? Dacă modulele utilizează constant datele și regulile interne ale celorlalte, separarea lor în servicii independente poate consolida o împărțire greșită.
4. Avem nevoie de scalare sau implementare independente? Dacă toate părțile sistemului sunt modificate și solicitate în mod similar, beneficiul independenței poate fi redus.
5. Avem instrumentele și competențele necesare pentru a întreține un sistem distribuit? Monitorizarea, jurnalele, urmărirea solicitărilor, implementările automate și procedurile de gestionare a defecțiunilor sunt elemente importante ale acestei decizii.
6. Cum vor fi gestionate datele și tranzacțiile? Separarea unui serviciu poate îngreuna menținerea atomicității operațiunilor care implică mai multe domenii. Trebuie planificat ce se întâmplă dacă o parte a procesului reușește, iar alta nu.
7. Justifică beneficiile costul total? Merită luate în calcul nu doar costurile de implementare, ci și cele pentru infrastructură, monitorizare, securitate, testare, întreținere și dezvoltarea competențelor.
8. Putem începe cu o schimbare mai mică? Uneori este suficient să reorganizăm modulele, să separăm un proces asincron sau să îmbunătățim automatizarea implementărilor. Cea mai mică schimbare care rezolvă eficient problema poate fi cel mai bun punct de plecare.
Cele mai frecvente greșeli în alegerea arhitecturii
Prima greșeală este confundarea modernității cu numărul de servicii. Microserviciile reprezintă o anumită modalitate de organizare a unui sistem, nu un scop în sine.
A doua este proiectarea arhitecturii pe baza unor previziuni care nu au fost încă validate în funcționarea reală a produsului. Merită să ne păstrăm posibilitatea de a evolua și să separăm alte componente atunci când apare o nevoie justificată.
A treia este fragmentarea sistemului fără stabilirea limitelor dintre domeniile de activitate. Dacă serviciile depind puternic unele de altele, rezultă o arhitectură distribuită care păstrează problemele monolitului, adăugând însă costurile comunicării prin rețea.
A patra este ignorarea aspectelor operaționale ale soluției. Fiecare serviciu nou trebuie implementat, monitorizat, securizat și diagnosticat. Fără procese adecvate, numărul tot mai mare de componente poate reduce fiabilitatea în loc să o îmbunătățească.
A cincea este încercarea de a rezolva problemele organizaționale doar prin tehnologie. Microserviciile pot sprijini independența echipelor, dar nu înlocuiesc o împărțire clară a responsabilităților, comunicarea și o bună gestionare a produsului.
Al șaselea este să tratezi monolitul modular ca pe o soluție temporară, de care trebuie să scapi cât mai repede. O aplicație monolitică bine proiectată poate rămâne alegerea potrivită mulți ani. Nu este nevoie să-i complici arhitectura fără un beneficiu concret.
Arhitectura ar trebui să evolueze odată cu produsul
Nu există o arhitectură care să fie cea mai bună pentru fiecare aplicație. Un produs mic, dezvoltat de o echipă de câteva persoane, are nevoi diferite față de o platformă care deservește multe domenii de business independente și este dezvoltată de mai multe echipe.
Un monolit poate oferi implementare simplă și o gestionare mai ușoară a operațiunilor. Un monolit modular permite organizarea structurii aplicației, păstrând această simplitate. Microserviciile pot oferi, în schimb, independență în dezvoltare, implementare și scalare, dacă produsul și organizația au într-adevăr nevoie de acest model.
Cel mai important este să identifici problema cu care se confruntă compania. Nu merită să introduci zeci de servicii doar pentru că așa arată arhitectura platformelor populare. Nici nu merită să te agăți cu încăpățânare de monolit dacă limitările sale blochează dezvoltarea produsului și poți identifica beneficii concrete în separarea unei părți a sistemului.
O decizie arhitecturală bună nu constă în a anticipa toate nevoile viitoare. Constă în construirea unei soluții care răspunde cerințelor actuale, fără să blocheze calea către schimbări rezonabile în viitor.
Înainte să împarți aplicația în microservicii, verifică dacă ai într-adevăr nevoie de servicii independente sau, în primul rând, de un sistem mai bine organizat. Această diferență poate determina nu doar arhitectura, ci și costurile și ritmul de dezvoltare a produsului.
Surse și lecturi suplimentare
-
AWS Well-Architected Framework: Alege cum să segmentezi volumul de lucru - compromisurile dintre arhitectura monolitică și cea bazată pe microservicii.
-
Microsoft Azure Architecture Center: Arhitectura bazată pe microservicii - complexitatea, comunicarea, testarea și gestionarea datelor în sistemele distribuite.
-
microservices.io: Cât de modular poate deveni monolitul tău? - organizarea monolitului în jurul domeniilor de business.
Articole Web24 conexe
-
De ce costă din ce în ce mai mult o funcționalitate nouă într-o aplicație veche?
-
Aplicația ta funcționează. Dar poate fi implementată în siguranță de 100 de ori pe zi?
Linkurile de mai sus duc la articole despre aspecte legate de modernizare, costurile dezvoltării și procesul de implementare. Acestea nu înlocuiesc o analiză separată a alegerii arhitecturii.
Lectură plăcută.
