Building Domain Driven Microservices

Chandra Ramalingam

Follow

Iulie 1, 2020 – 18 min citește

Credite imagine:

Termenul „micro” din Microservicii, deși indică dimensiunea unui serviciu, nu este singurul criteriu care face ca o aplicație să fie un Microserviciu. Atunci când echipele trec la o arhitectură bazată pe microservicii, acestea urmăresc să își sporească agilitatea – să implementeze caracteristici în mod autonom și frecvent. Este greu de stabilit o singură definiție concisă a acestui stil arhitectural. Mi-a plăcut această scurtă definiție de la Adrian Cockcroft – „arhitectura orientată pe servicii compusă din elemente cuplate lejer care au contexte delimitate.”

Deși aceasta definește o euristică de proiectare la nivel înalt, arhitectura Microservicii are câteva caracteristici unice care o diferențiază de arhitectura orientată pe servicii de altădată. Câteva dintre aceste caracteristici, mai jos. Acestea și alte câteva sunt bine documentate – articolul lui Martin Fowler și cartea Building Microservices a lui Sam Newman, pentru a numi câteva.

  1. Serviciile au granițe bine definite, centrate pe contextul de afaceri, și nu pe abstracțiuni tehnice arbitrare
  2. Ascundeți detaliile de implementare și expuneți funcționalitatea prin interfețe care dezvăluie intenția
  3. Serviciile nu își împărtășesc structurile interne dincolo de granițele lor. De exemplu, nu se partajează bazele de date.
  4. Serviciile sunt rezistente la eșecuri.
  5. Echipele își dețin funcțiile în mod independent și au capacitatea de a lansa modificări în mod autonom
  6. Echipele adoptă o cultură a automatizării. De exemplu, testarea automatizată, integrarea continuă și livrarea continuă

Pe scurt, putem rezuma acest stil de arhitectură după cum urmează:

Arhitectură orientată pe servicii cuplate lejer, în care fiecare serviciu este închis într-un context delimitat bine definit, permițând livrarea rapidă, frecventă și fiabilă a aplicațiilor.

Domain-driven design and Bounded contexts

Puterea microserviciilor provine din definirea clară a responsabilității acestora și delimitarea limitei dintre ele. Scopul aici este de a construi o coeziune ridicată în interiorul limitei și un cuplaj redus în afara acesteia. Altfel spus, lucrurile care tind să se schimbe împreună ar trebui să aparțină împreună. La fel ca în multe probleme din viața reală, acest lucru este mai ușor de spus decât de făcut – afacerile evoluează, iar ipotezele se schimbă. Prin urmare, capacitatea de refactorizare este un alt lucru critic care trebuie luat în considerare atunci când se proiectează sisteme.

Proiectarea bazată pe domeniu (DDD) este un instrument cheie și, în opinia noastră, necesar atunci când se proiectează microservicii, fie că este vorba de spargerea unui monolit sau de implementarea unui proiect greenfield. Domain-driven design, făcut celebru de Eric Evans prin cartea sa , este un set de idei, principii și modele care ajută la proiectarea sistemelor software pe baza modelului de bază al domeniului de afaceri. Dezvoltatorii și experții din domeniu lucrează împreună pentru a crea modele de afaceri într-un limbaj comun omniprezent. Ei leagă apoi aceste modele de sistemele în care au sens, stabilesc protocoale de colaborare între aceste sisteme și echipele care lucrează la aceste servicii. Mai important, ei proiectează contururile conceptuale sau granițele dintre sisteme.

Proiectarea microserviciilor se inspiră din aceste concepte, deoarece toate aceste principii ajută la construirea unor sisteme modulare care se pot schimba și evolua independent unele de altele.

Înainte de a merge mai departe, să trecem rapid în revistă câteva dintre terminologiile de bază ale DDD. O prezentare completă a Domain-Driven Design este în afara scopului acestui blog. Recomandăm cu căldură cartea lui Eric Evans tuturor celor care încearcă să construiască microservicii

Domeniu: Reprezintă ceea ce face o organizație. În exemplul de mai jos, ar fi Retail sau eCommerce.

Subdomeniu: O organizație sau o unitate de afaceri din cadrul unei organizații. Un domeniu este compus din mai multe subdomenii.

Limbaj omniprezent: Acesta este limbajul utilizat pentru a exprima modelele. În exemplul de mai jos, Item este un model care aparține limbajului omniprezent al fiecăruia dintre aceste subdomenii. Dezvoltatorii, managerii de produs, experții în domeniu și părțile interesate din domeniul afacerilor sunt de acord asupra aceluiași limbaj și îl folosesc în artefactele lor – cod, documentație de produs și așa mai departe.

Figură 1. Subdomenii și contexte delimitate în domeniul eCommerce

Contexte delimitate: Domain-driven design definește contextele delimitate (Bounded contexts) ca fiind „Cadrul în care apare un cuvânt sau un enunț care îi determină semnificația”. Pe scurt, aceasta înseamnă granița în interiorul căreia un model are sens. În exemplul de mai sus, „Articol” capătă un înțeles diferit în fiecare dintre aceste contexte. În contextul Catalog, un Articol înseamnă un produs care poate fi vândut, în timp ce, în contextul Coș, înseamnă articolul pe care clientul l-a adăugat în coșul său. În contextul Fulfillment, înseamnă un articol din depozit care va fi expediat clientului. Fiecare dintre aceste modele este diferit și fiecare are o semnificație diferită și poate conține atribute diferite. Prin separarea și izolarea acestor modele în limitele lor respective, putem exprima modelele în mod liber și fără ambiguitate.

Nota: Este esențial să înțelegem distincția dintre subdomeniile și contextele delimitate. Un subdomeniu aparține spațiului problemei, adică modul în care afacerea dvs. vede problema, în timp ce contextele delimitate aparțin spațiului soluției, adică modul în care vom implementa soluția la problemă. Teoretic, fiecare subdomeniu poate avea mai multe contexte delimitate, deși noi ne străduim să avem un singur context delimitat pentru fiecare subdomeniu.

Cum sunt Microserviciile legate de contextele delimitate

Acum, unde se încadrează Microserviciile? Este corect să spunem că fiecare context delimitat se mapează cu un microserviciu? Da și nu. Vom vedea de ce. Pot exista cazuri în care granița sau conturul contextului delimitat este destul de mare.

Fig 2. Contextul delimitat și microserviciile

Considerați exemplul de mai sus. Contextul delimitat Pricing are trei modele distincte – Price, Priced items și Discounts, fiecare fiind responsabil de prețul unui articol din catalog, de calcularea prețului total al unei liste de articole și, respectiv, de aplicarea reducerilor. Am putea crea un singur sistem care să înglobeze toate modelele de mai sus, dar acesta ar putea deveni o aplicație nejustificat de mare. Fiecare dintre modelele de date, după cum s-a menționat mai devreme, are invarianții și regulile sale de afaceri. În timp, dacă nu suntem atenți, sistemul ar putea deveni o Mare minge de noroi cu granițe obscure, responsabilități care se suprapun și, probabil, să ne întoarcem de unde am pornit – un monolit.

O altă modalitate de a modela acest sistem este de a separa, sau de a grupa modelele conexe în microservicii separate. În DDD, aceste modele – Preț, Articole cu preț și Reduceri – se numesc Agregate. Un agregat este un model de sine stătător care compune modele conexe. Ați putea schimba starea unui agregat numai printr-o interfață publicată, iar agregatul asigură coerența și faptul că invarianții se mențin.

În mod normal, un agregat este un grup de obiecte asociate tratate ca o unitate pentru modificările de date. Referințele externe sunt limitate la un singur membru al AGREGATULUI, desemnat drept rădăcină. Un set de reguli de coerență se aplică în limitele AGREGATULUI.

Figura 3. Microservicii în contextul stabilirii prețurilor

Încă o dată, nu este necesar să se modeleze fiecare agregat ca microserviciu distinct. S-a dovedit a fi așa pentru serviciile (agregatele) din Fig. 3, dar aceasta nu este neapărat o regulă. În unele cazuri, poate avea sens să găzduim mai multe agregate într-un singur serviciu, în special atunci când nu înțelegem pe deplin domeniul de activitate. Un lucru important de reținut este faptul că consistența poate fi garantată doar în cadrul unui singur agregat, iar agregatele pot fi modificate doar prin intermediul interfeței publicate. Orice încălcare a acestora comportă riscul de a se transforma într-o mare minge de noroi.

Hărți de context – O modalitate de a trasa limite precise ale microserviciilor

O altă trusă de instrumente esențială în arsenalul dumneavoastră este conceptul de hărți de context – din nou, din Domain Driven Design. Un monolit este, de obicei, compus din modele disparate, în cea mai mare parte strâns cuplate – probabil că modelele cunosc detaliile intime ale unuia și altuia, modificarea unuia ar putea provoca efecte secundare asupra altuia și așa mai departe. Pe măsură ce descompuneți monolitul, este vital să identificați aceste modele – agregate în acest caz – și relațiile dintre ele. Hărțile contextuale ne ajută să facem exact acest lucru. Acestea sunt utilizate pentru a identifica și defini relațiile dintre diferitele contexte și agregate delimitate. În timp ce contextele delimitate definesc limitele unui model – Preț, Reduceri etc. în exemplul de mai sus, hărțile contextuale definesc relațiile dintre aceste modele și dintre diferitele contexte. După ce identificăm aceste dependențe, putem determina modelul corect de colaborare între echipele care vor implementa aceste servicii.

O explorare completă a hărților de context depășește scopul acestui blog, dar o vom ilustra cu un exemplu. Diagrama de mai jos reprezintă diferitele aplicații care gestionează plățile pentru o comandă de eCommerce.

  1. Contextul coșului de cumpărături se ocupă de autorizările online ale unei comenzi; Contextul comenzii procesează procesele de plată post-achiziție, cum ar fi Decontările; Centrul de contact se ocupă de orice excepții, cum ar fi reluarea plăților și schimbarea metodei de plată utilizate pentru comandă
  2. De dragul simplității, să presupunem că toate aceste contexte sunt implementate ca servicii separate
  3. Toate aceste contexte încapsulează același model.
  4. Rețineți că aceste modele sunt identice din punct de vedere logic. Adică, toate urmează același limbaj de domeniu Ubiquitous – metode de plată, autorizații și decontări. Doar că ele fac parte din contexte diferite.

Un alt semn că același model este răspândit în diferite contexte este faptul că toate acestea se integrează direct cu un singur gateway de plată și fac aceleași operații ca și celelalte

Fig 4. O hartă a contextului incorect definită

Redefinirea limitelor serviciilor – Maparea agregatelor către contextele corecte

Există câteva probleme care sunt foarte evidente în proiectarea de mai sus (Fig. 4). Plățile agregate fac parte din mai multe contexte. Este imposibil să se impună invariantele și coerența între diverse servicii, ca să nu mai vorbim de problemele de concurență între aceste servicii. De exemplu, ce se întâmplă dacă centrul de contact modifică metoda de plată asociată comenzii în timp ce serviciul Comenzi încearcă să înregistreze decontarea unei metode de plată transmise anterior. De asemenea, rețineți că orice modificare a gateway-ului de plată ar forța modificări la mai multe servicii și, potențial, la numeroase echipe, deoarece diferite grupuri ar putea deține aceste contexte.

Cu câteva ajustări și alinierea agregatelor la contextele corecte, obținem o reprezentare mult mai bună a acestor subdomenii – Fig. 5. Sunt multe lucruri care s-au schimbat. Să trecem în revistă modificările:

  1. Agregatul Plăți are o nouă casă – Serviciul de plăți. Acest serviciu abstractizează, de asemenea, gateway-ul de plată de celelalte servicii care necesită servicii de plată. Deoarece un singur context delimitat deține acum un agregat, invarianții sunt ușor de gestionat; toate tranzacțiile au loc în cadrul aceleiași limite a serviciului, ajutând la evitarea oricăror probleme de concurență.
  2. Agregatul de plăți utilizează un strat anti-corupție (ACL) pentru a izola modelul de bază al domeniului de modelul de date al gateway-ului de plăți, care este de obicei un furnizor terț și poate fi obligat să se schimbe. Vom aprofunda proiectarea aplicației unui astfel de serviciu folosind modelul Ports and Adapters într-o postare viitoare. Stratul ACL conține de obicei adaptoare care transformă modelul de date al gateway-ului de plată în modelul de date agregat al serviciului Plăți.
  3. Serviciul Coș de cumpărături apelează serviciul Plăți prin apeluri directe la API, deoarece serviciul Coș de cumpărături poate fi nevoit să finalizeze autorizarea plății în timp ce clienții se află pe site
  4. Rețineți interacțiunea dintre serviciul Comenzi și serviciul Plăți. Serviciul Comenzi emite un eveniment de domeniu (mai multe despre acest lucru mai târziu în acest blog). Serviciul de plăți ascultă acest eveniment și finalizează decontarea comenzii
  5. Serviciul centrului de contact poate avea mai multe agregate, dar pe noi ne interesează doar agregatul Comenzi pentru acest caz de utilizare. Acest serviciu emite un eveniment atunci când se schimbă metoda de plată, iar serviciul Plăți reacționează la acesta prin anularea cardului de credit utilizat anterior și procesarea noului card de credit.

Figura 5. Harta contextului redefinit

De obicei, o aplicație monolitică sau o aplicație moștenită are multe agregate, adesea cu limite care se suprapun. Crearea unei hărți de context a acestor agregate și a dependențelor lor ne ajută să înțelegem contururile oricăror noi microservicii pe care le vom smulge din aceste monolite. Rețineți, succesul sau eșecul arhitecturii de microservicii depinde de un cuplaj redus între agregate și de o coeziune ridicată în cadrul acestor agregate.

Este, de asemenea, important să rețineți că contextele delimitate sunt ele însele unități coezive adecvate. Chiar dacă un context are mai multe agregate, întregul context, împreună cu agregatele sale, poate fi compus într-un singur microserviciu. Considerăm că această euristică este deosebit de utilă pentru domeniile care sunt puțin obscure – gândiți-vă la o nouă linie de afaceri în care se aventurează organizația. Este posibil să nu aveți o perspectivă suficientă asupra limitelor corecte de separare, iar orice descompunere prematură a agregatelor poate duce la refactorizări costisitoare. Imaginați-vă că trebuie să fuzionați două baze de date într-una singură, împreună cu migrarea datelor, pentru că s-a întâmplat să descoperim că două agregate aparțin împreună. Dar asigurați-vă că aceste agregate sunt suficient de izolate prin interfețe, astfel încât să nu cunoască detaliile complicate ale celuilalt.

Event Storming – O altă tehnică pentru a identifica limitele serviciilor

Event Storming este o altă tehnică esențială pentru a identifica agregatele (și, prin urmare, microserviciile) într-un sistem. Este un instrument util atât pentru a sparge monoliții, cât și atunci când se proiectează un ecosistem complex de microservicii. Am folosit această tehnică pentru a descompune una dintre aplicațiile noastre complexe și intenționăm să acoperim experiențele noastre cu Event Storming într-un blog separat. Pentru scopul acestui blog, dorim să oferim o prezentare rapidă la nivel înalt. Vă rugăm să urmăriți videoclipul lui Alberto Brandelloni pe această temă dacă sunteți interesat să explorați mai departe.

În câteva cuvinte, Event Storming este un exercițiu de brainstorming între echipele care lucrează la o aplicație – în cazul nostru, un monolit – pentru a identifica diversele evenimente și procese de domeniu care se întâmplă în cadrul unui sistem. De asemenea, echipele identifică agregatele sau modelele pe care aceste evenimente le afectează și orice impact ulterior al acestora. Pe măsură ce echipele fac acest exercițiu, ele identifică diferite concepte care se suprapun, limbajul ambiguu al domeniului și procesele de afaceri conflictuale. Ele grupează modelele conexe, redefinesc agregatele și identifică procesele duplicate. Pe măsură ce avansează cu acest exercițiu, contextele delimitate cărora le aparțin aceste agregate devin clare. Atelierele Event Storming sunt utile dacă toate echipele se află într-o singură încăpere – fizică sau virtuală – și încep să cartografieze evenimentele, comenzile și procesele pe o tablă albă de tip scrum. La sfârșitul acestui exercițiu, mai jos sunt rezultatele obișnuite:

  1. Listă redefinită de Agregate. Aceștia devin potențial noi microservicii
  2. Evenimente de domeniu care trebuie să circule între aceste microservicii
  3. Comenzi care sunt invocări directe de la alte aplicații sau utilizatori

Am prezentat mai jos un exemplu de tablă la sfârșitul unui atelier de Event Storming. Este un exercițiu de colaborare excelent pentru ca echipele să se pună de acord asupra agregatelor corecte și a contextelor delimitate. Pe lângă faptul că este un exercițiu grozav de consolidare a echipei, echipele ies din această sesiune cu o înțelegere comună a domeniului, cu un limbaj omniprezent și cu limite precise ale serviciilor.

Fig 6. Tabloul Event Storming

Comunicare între microservicii

Pentru a recapitula rapid, un monolit găzduiește mai multe agregate în cadrul unei singure limite de proces. Prin urmare, gestionarea consistenței agregatelor în cadrul acestei limite este posibilă. De exemplu, dacă un client plasează o comandă, putem să descreștem inventarul articolelor, să trimitem un e-mail clientului – toate acestea în cadrul unei singure tranzacții. Toate operațiunile vor reuși sau vor eșua. Dar, pe măsură ce spargem monolitul și răspândim agregatele în diferite contexte, vom avea zeci sau chiar sute de microservicii. Procesele care până acum existau în cadrul limitei unice a unui monolit sunt acum răspândite în mai multe sisteme distribuite. Realizarea integrității și consistenței tranzacționale în toate aceste sisteme distribuite este foarte dificilă și are un cost – disponibilitatea sistemelor.

Microserviciile sunt, de asemenea, sisteme distribuite. Prin urmare, teorema CAP se aplică și în cazul acestora – „un sistem distribuit poate oferi doar două dintre cele trei caracteristici dorite: consistență, disponibilitate și toleranță la partiții („C”, „A” și „P” din CAP)”. În sistemele din lumea reală, toleranța la partiții nu este negociabilă – rețeaua nu este fiabilă, mașinile virtuale pot cădea, latența între regiuni se poate înrăutăți și așa mai departe.

Așa că ne lasă să alegem între Disponibilitate sau Consistență. Acum, știm că, în orice aplicație modernă, nici sacrificarea disponibilității nu este o idee bună.

Fig 7. Teorema CAP

Proiectați aplicații în jurul unei eventuale coerențe

Dacă încercați să construiți tranzacții în mai multe sisteme distribuite, veți ajunge din nou în țara monolitului. Numai că de data aceasta va fi cel mai rău tip, un monolit distribuit. Dacă unul dintre sisteme devine indisponibil, întregul proces devine indisponibil, ceea ce duce adesea la o experiență frustrantă pentru clienți, la promisiuni eșuate și așa mai departe. În plus, modificările aduse unui serviciu pot implica, de obicei, modificări ale unui alt serviciu, ceea ce duce la implementări complexe și costisitoare. Prin urmare, este mai bine să proiectăm aplicații care să adapteze cazurile de utilizare pentru a tolera un pic de inconsecvență în favoarea disponibilității. Pentru exemplul de mai sus, putem face ca toate procesele să fie asincrone și, prin urmare, în cele din urmă consecvente. Putem trimite e-mailuri în mod asincron, independent de celelalte procese; dacă un articol promis nu este disponibil în depozit mai târziu, articolul ar putea fi comandat din nou sau am putea să nu mai acceptăm comenzi pentru acel articol dincolo de un anumit prag.
Ocazional, este posibil să întâlniți un scenariu care ar putea necesita tranzacții puternice de tip ACID între două agregate în limite de proces diferite. Acesta este un semn excelent pentru a reexamina aceste agregate și poate pentru a le combina într-unul singur. Event Storming și hărțile de context vor ajuta la identificarea timpurie a acestor dependențe înainte de a începe să împărțim aceste agregate în diferite limite de proces. Fuzionarea a două microservicii într-unul singur este costisitoare, iar acesta este un lucru pe care ar trebui să ne străduim să îl evităm.

Favorizați arhitectura bazată pe evenimente

Microserviciile pot emite modificări esențiale care se întâmplă în agregatele lor. Acestea se numesc evenimente de domeniu și orice servicii care sunt interesate de aceste modificări pot asculta aceste evenimente și pot lua măsurile respective în cadrul domeniilor lor. Această metodă evită orice cuplaj comportamental – un domeniu nu prescrie ceea ce ar trebui să facă celelalte domenii, precum și cuplajul temporal – finalizarea cu succes a unui proces nu depinde de faptul că toate sistemele trebuie să fie disponibile în același timp. Acest lucru, desigur, va însemna că sistemele vor fi în cele din urmă consecvente.

Figura 8. Arhitectura bazată pe evenimente

În exemplul de mai sus, serviciul Comenzi publică un eveniment – Comandă anulată. Celelalte servicii care s-au abonat la eveniment își procesează funcțiile de domeniu respective: Serviciul de plată rambursează banii, Serviciul de inventariere ajustează inventarul de articole și așa mai departe. Câteva lucruri de reținut pentru a asigura fiabilitatea și reziliența acestei integrări:

  1. Producătorii trebuie să se asigure că produc un eveniment cel puțin o dată. În cazul în care nu reușesc să facă acest lucru, ei ar trebui să se asigure că există un mecanism de rezervă pentru a declanșa din nou evenimentele
  2. Consumatorii ar trebui să se asigure că consumă evenimentele într-un mod idempotent. Dacă același eveniment se repetă, nu ar trebui să existe niciun efect secundar la capătul consumatorului. Evenimentele pot, de asemenea, să sosească în afara secvenței. Consumatorii pot utiliza câmpurile Timestamp sau numerele de versiune pentru a garanta unicitatea evenimentelor.

Este posibil să nu fie întotdeauna posibilă utilizarea integrării bazate pe evenimente din cauza naturii unor cazuri de utilizare. Vă rugăm să aruncați o privire la integrarea dintre serviciul Coș și serviciul Plată. Este o integrare sincronă și, prin urmare, are câteva lucruri la care trebuie să fim atenți. Este un exemplu de cuplare comportamentală – serviciul Cart apelează probabil un API REST de la serviciul Payment și îl instruiește să autorizeze plata pentru o comandă, și de cuplare temporală – serviciul Payment trebuie să fie disponibil pentru ca serviciul Cart să accepte o comandă. Acest tip de cuplare reduce autonomia acestor contexte și poate crea o dependență nedorită. Există câteva modalități de a evita acest cuplaj, dar cu toate aceste opțiuni, vom pierde capacitatea de a oferi feedback instantaneu clienților.

  1. Convertiți API-ul REST într-o integrare bazată pe evenimente. Dar este posibil ca această opțiune să nu fie disponibilă dacă serviciul de plată expune doar un API REST
  2. Serviciul de coș acceptă o comandă instantaneu și există o sarcină de lucru pe loturi care preia comenzile și apelează API-ul serviciului de plată
  3. Serviciul de coș produce un eveniment local care apoi apelează API-ul serviciului de plată

O combinație a celor de mai sus cu reintrări în caz de eșecuri și indisponibilitate a dependenței din amonte – serviciul de plată – poate duce la o proiectare mult mai rezistentă. De exemplu, integrarea sincronă între serviciile „Cart” și „Payment” poate fi susținută de un eveniment sau de reintrări bazate pe loturi în caz de eșecuri. Această abordare are un impact suplimentar asupra experienței clienților – este posibil ca aceștia să fi introdus detalii de plată incorecte, iar noi nu le vom avea online atunci când vom procesa plățile offline. Sau poate exista un cost suplimentar pentru afacere pentru a recupera plățile eșuate. Dar, după toate probabilitățile, beneficiile serviciului „Coș de cumpărături”, care este rezistent la indisponibilitatea sau la defecțiunile serviciului de plăți, depășesc neajunsurile. De exemplu, putem notifica clienții în cazul în care nu suntem în măsură să colectăm plățile offline. Pe scurt, există compromisuri între experiența utilizatorului, reziliența și costurile de operare și este înțelept să proiectăm sisteme, ținând cont de aceste compromisuri.

Evitați orchestrarea între servicii pentru nevoile de date specifice consumatorilor

Unul dintre anti-patternurile din orice arhitectură orientată pe servicii este ca serviciile să se adapteze la modelele de acces specifice ale consumatorilor. De obicei, acest lucru se întâmplă atunci când echipele de consumatori lucrează îndeaproape cu echipele de servicii. În cazul în care echipa lucrează la o aplicație monolitică, aceasta ar crea adesea o singură API care traversează diferite granițe de agregate, cuplând astfel strâns aceste agregate. Să luăm un exemplu. Să presupunem că pagina Detalii comandă din aplicațiile web și mobilă trebuie să afișeze pe o singură pagină atât detaliile unei comenzi, cât și detaliile rambursărilor procesate pentru comanda respectivă. Într-o aplicație monolitică, un API GET pentru comenzi – presupunând că este vorba de un API REST – interoghează comenzi și rambursări împreună, consolidează ambele agregate și trimite un răspuns compozit către apelanți. Este posibil să se facă acest lucru fără prea multe costuri suplimentare, deoarece agregatele aparțin aceleiași limite de proces. Astfel, consumatorii pot obține toate datele necesare într-un singur apel.

Dacă Ordinele și Rambursările fac parte din contexte diferite, datele nu mai sunt prezente în cadrul unui singur microserviciu sau al unei singure limite de agregate. O opțiune pentru a păstra aceeași funcționalitate pentru consumatori este aceea de a face serviciul Order responsabil de apelarea serviciului Refunds și de a crea un răspuns compozit. Această abordare cauzează mai multe probleme:

1. Serviciul de comandă se integrează acum cu un alt serviciu doar pentru a sprijini consumatorii care au nevoie de datele de rambursare împreună cu datele de comandă. Serviciul de comenzi este mai puțin autonom acum, deoarece orice modificare a agregatului Refunds va duce la o modificare a agregatului Order.

2. Serviciul de comenzi are o altă integrare și, prin urmare, un alt punct de eșec de care trebuie să se țină cont – dacă serviciul Refunds nu funcționează, poate serviciul Order să trimită în continuare date parțiale și consumatorii pot eșua cu grație?

3. Dacă consumatorii au nevoie de o modificare pentru a prelua mai multe date din agregatul Refunds, două echipe sunt implicate acum pentru a face această modificare

4. Acest model, dacă este urmat în întreaga platformă, poate duce la o rețea complicată de dependențe între diferitele servicii de domeniu, toate acestea pentru că aceste servicii răspund modelelor specifice de acces ale apelanților.

Backend for Frontends (BFFs)

O abordare pentru a atenua acest risc este de a lăsa echipele de consumatori să gestioneze orchestrarea între diferitele servicii de domeniu. La urma urmei, apelanții cunosc mai bine modelele de acces și pot deține controlul complet asupra oricăror modificări ale acestor modele. Această abordare decuplează serviciile de domeniu de nivelul de prezentare, permițându-le acestora să se concentreze pe procesele de bază ale afacerii. Dar dacă aplicațiile web și mobile încep să apeleze direct diferite servicii în loc de o singură API compozită de la monolit, aceasta ar putea cauza costuri suplimentare de performanță pentru aceste aplicații – apeluri multiple pe rețele cu lățime de bandă mai mică, procesarea și fuzionarea datelor de la diferite API-uri și așa mai departe.

În schimb, se poate folosi un alt model numit Backend for Front-ends. În acest model de proiectare, un serviciu back-end creat și gestionat de consumatori – în acest caz, echipele web și mobile – se ocupă de integrarea între mai multe servicii de domeniu doar pentru a reda clienților experiența front-end. Echipele web și mobile pot acum să proiecteze contractele de date în funcție de cazurile de utilizare pe care le satisfac. Ele pot folosi chiar GraphQL în loc de API-uri REST pentru a interoga în mod flexibil și a primi înapoi exact ceea ce au nevoie. Este important de reținut că acest serviciu este deținut și întreținut de echipele de consumatori și nu de echipele care dețin serviciile de domeniu. Echipele front-end pot acum să optimizeze în funcție de nevoile lor – o aplicație mobilă poate solicita o sarcină utilă mai mică, poate reduce numărul de apeluri din aplicația mobilă și așa mai departe. Aruncați o privire la vederea revizuită a orchestrației de mai jos. Serviciul BFF apelează acum ambele servicii de domeniu Comenzi și Rambursări pentru cazul său de utilizare.

Figura 9. Backend pentru Frontends

De asemenea, este util să construiți serviciul BFF din timp, înainte de a rupe o multitudine de servicii din monolit. În caz contrar, fie serviciile de domeniu vor trebui să susțină orchestrarea interdomeniu, fie aplicațiile web și mobile vor trebui să apeleze mai multe servicii direct din front-end. Ambele opțiuni vor duce la supraperformanță, muncă de aruncat și lipsă de autonomie între echipe.

Concluzie

În acest blog, am abordat diverse concepte, strategii și euristici de proiectare pe care trebuie să le luăm în considerare atunci când ne aventurăm în lumea microserviciilor, mai exact atunci când încercăm să rupem un monolit în mai multe microservicii bazate pe domenii. Multe dintre acestea sunt subiecte vaste de sine stătătoare și nu cred că am făcut suficientă dreptate pentru a le explica în detaliu, dar am dorit să prezentăm câteva dintre subiectele esențiale și experiența noastră în adoptarea acestora. Secțiunea Lecturi suplimentare (link) are câteva referințe și un conținut util pentru oricine dorește să urmeze această cale.

Update: Următoarele două bloguri din serie au apărut. Aceste două bloguri discută despre implementarea microserviciului Cart, cu exemple de cod, folosind principiile Domain-Driven Design și modelele de design Ports și Adapters. Obiectivul principal al acestor bloguri este de a demonstra modul în care aceste două principii/modele ne ajută să construim aplicații modulare care sunt agile, testabile și refactorizabile – pe scurt, să fie capabile să răspundă la mediul în ritm rapid în care operăm cu toții.

Implementarea Microserviciului Cart folosind Domain Driven Design și Ports and Adapters Pattern – Partea 1

Implementarea Microserviciului Cart folosind Domain Driven Design și Ports and Adapters Pattern – Partea 2

Lecturi suplimentare

1. Eric Evans’ Domain Driven Design

2. Vaughn Vernon’s Implementing Domain Driven Design

3. Martin Fowler’s article on Microservices

4. Sam Newman’s Building Microservices

5. Sam Newman’s Building Microservices

5. Event storming

7. Backend for Frontends

8. Fallacies of distributed computing

8. Fallacies of distributed computing8.

Lasă un răspuns

Adresa ta de email nu va fi publicată.