Computers are hard - interviu DHH

ca tot e discutia de servicii (voiam sa intreb ieri, apoi m-am razgandit ca sa nu poluez topicul, intre timp m-am ra-razgandit :))) ).

Avem 3 microservicii : Orders, Payments, Users, nici unul nu stie de existenta celuilalt. Dar, un user are un order.

Cum stii in db-ul orders, in tabela orders (meh, n-am gasit alte denumiri db/table) ca orderul respectiv apartine user-ului Y?

  • In tabela orders gasim un userId venit din db-ul Users, dar nu ca FK pentru ca este imposibil, ci ca un camp normal?
  • Avem un transactionId generat de microserviciul care porneste actiunea, si il comunica si celuilalt? (fara sa am habar, varianta asta imi place mai mult), astfel in tabela orders vom gasi un transactionId, iar in db-ul Users vom gasi un UserTransactions cu userId, transactionId, poate si un createdDate?

*pentru joinuri intre db-uri, userId e o varianta mai usoara, dar nici cu transactionId nu e imposibil (un join in plus) :crazy_face:

merci

// edit: m-am impotmolit. Imi imaginam un getOrders pentru UserProfile-ul meu. trebuie sa ma duc in dbUsers prima data, sa gasesc UserTransactions de tip ‘Orders’ (deci pe langa cele 3 campuri enumerate mai sus, mai am nevoie si de un ServiceType), sa iau toate transactionIds, dupa care sa ma duc in dbOrders, tabela Orders si sa iau toate orderele cu transactionId care e inclus in ids. :woozy_face: Asta imi suna a compromis microservicii vs monolit

Am găsit și rezolvarea. Nu-mi place cum suna ‘duplicarea datelor’ :speak_no_evil:

Fun fact: in multe DB-uri mari sunt interzise sau disable-uite foreign key-urile.
La o anumita “scara” (la care ar trebui sa te uiti cand ai microservicii) tot pe acolo ajungi.

Join-uri de genul la care te referi au sens mai mult intr-o context OLAP decat OLTP. Ce se intampla e ca exista un sistem secundar - un data lake, in care toate sistemele astea toarna datele si acolo se fac astfel de analize.

2 Likes

Smecheria e ca in microserviciul User pastrezi toate informatiile de user, inclusiv pe maica-sa. In Orders te intereseaza doar ca individul 5 a comandat 3 piscoturi care costa 1 leu, cu discount de Halloween si cupon taiat din ziar. Focus pe comanda, deci, ca e logica destula doar pentru asta. Castigul e ca userul e un serviciu in Amazon, Orderul in Shopify iar magazinul e in Wordpress (scuze)

Intr-o arhitectura cu microservicii sau una in care se urmeaza cat de cat ideile si practicile Domain Driven Design, nu se mai vorbeste atat de mult despre baze de date, cum si unde stocam datele.
Arhitectura nu mai este data-centric, ci domain-centric (sau business centric). Te intereseaza sa transpui in cod cat mai bine business-ul respectiv.

Astfel, focusul se schimba de la baze de date si foreign keys la Entities, Aggregates, Bounded Contexts, etc.

Intr-un astfel de sistem nu mai poti avea “hard consistency pe tranzactii across domains” , ci trebuie sa adopti “eventual consistency”, adica ai operatii tranzactionale doar atata timp cat esti intr-un “bounded context”. Interactiunea sau integrarea cu alte “bounded contexts (domains)” se face prin mesaje asincrone (RabbitMQ, Kafka, Azure Service Bus, etc.) sau prin REST APIs.
Si da, fix asa se face, share-uiesti doar ID-uri cu alte “bounded contexts” sau duplici anumite date, nu e nimic gresit in asta. Order poate insemna ceva in contextul UserService si cu totul altceva (avand mult mai multe detalii) in contextul OrderService.

Cand ai nevoie de date mai detaliate, fie faci un apel pe baza ID-ului catre serviciul care detine datele respective, fie faci niste “data lake-uri” cum a spus Horia mai sus in care versi date agregate, iar sincronizarea se face tot prin mesaje asincrone. Astfel poti sa separi complet fluxurile de citire de cele de scriere.

Partea frumoasa e cand folosesti baze de date no-sql pentru scriere, care iti pot asigura viteza constanta de scriere INDIFERENT de volumul de date, si baze de date relationale pentru citire (data lake-uri) in care procesezi si versi datele optimizat pentru citire.
Un avantaj al microserviciilor este ca poti folosi tehnologii diferite in fiecare serviciu, in functie de specificul acelui serviciu.

1 Like

Astia cu normalizarea bazelor de date si-au batut capul degeaba.

Experimental pot sa spun ca e foarte gresit sa ai date duplicate, am avut de-a face cu o baza de date unde aveau date duplicate… problema a fost ca le aveau in tspe locuri diferite si erau… tspe versiuni, nu aveau aceeasi valoare. Nu mai puteai sa stii care e valoarea corecta. Si macar daca ar fi fost in trei locuri si in doua din ele identice, dar nu, erau in patru locuri si in toate patru erau diferite intre ele.

Cand vorbim despre microservicii nu mai vorbim despre o singura baza de date, ci de mai multe baze de date, eventual tehnologii diferite de baze de date.
Evident ca intr-o baza de date asiguri normalizarea datelor, iar fiecare domeniu (bounded context) este owner pe datele respective. Acolo nu ai date duplicate.

Dar poti avea situatii cand, la citire, pentru afisarea datelor mai rapid intr-un anumit context/scenariu, este mai usor sa duplici niste date intr-o baza de date a altui serviciu.

Sau scenariul “clasic” cand vrei sa separi complet fluxurile de scriere de cele de citire. Cum faci asta?
Storage-ul in care scri poate fi un no-sql sau un “append-only store” (event sourcing) in care salvezi toate evenimentele care se intampla in sistemul tau, in ordinea in care se intampla.
In astfel de sisteme, acest store de evenimente reprezinta “single point of truth”, dar cum procesezi si afisezi aceste miliarde de evenimente in diverse aplicatii pe care le construiesti?

Nu poti sa procesezi de fiecare data toate evenimentele pentru ca devine extrem de costisitor odata ce ai foarte multe evenimente in sistem, asa ca se construiesc servicii de procesare care varsa datele in alte baze de date, in alt format. Poti spune ca aceste date sunt duplicate, pentru ca ele mai exista si in store-ul de baza, desi in alt format.
Sau se fac snapshot-uri din cand in cand care se salveaza in baze de date diferite, iar apoi se proceseaza doar evenimentele recente.

1 Like

Mare parte din proiectele de tip monolit spart din microservicii pe care le-am vazut erau de fapt monoliti distribuiti, direct GG. Oricum de la un punct totul devine un imens circle jerk intre ingineri care incearca ei sa aplice toate abordarile, tehnologiile si patternurile despre care au auzit, fara sa le inteleaga si fara sa cunoasca contextul in care s-au luat anumite hotarari de a implementa un pattern.

3 Likes

Da, cred ca e mirific sa ai tspe baze de date cu ‘aceleasi date’ care sa fie de fapt foarte diferite intre ele :slight_smile:

Nu ai tspe baze de date cu “aceleasi” date, ai o baza de date per serviciu/domeniu/bounded context.
Si nu sunt date duplicate, ci date specifice domeniului respectiv.
Date “duplicate” dar in alt format poti avea in baze de date agregate (sau data lake-uri), ceea ce are sens pentru ca acele baze de date sunt optimizate pentru diverse scenarii de citire (reporting, statistics, etc.).

Ok, fie, datele duplicate nu sunt date duplicate :slight_smile:

Facem analiza pe text acum?
Suntem pe un forum, nu scriu un articol stiintific si cred ca in general ai inteles ce am vrut sa zic.

Se poate vorbi despre normalizare la baze de date nosql? Atunci sa aruncam toate bazele de date nosql la gunoi pentru ca nu sunt normalizate ca cele relationale.

Din acelasi motiv pentru care nu aruncam la gunoi orice nu e baza de date… dar hai sa divagam.

Si mai ales sa ne imaginam ca exista gloante de argint care rezolva magic problemele.

Cine isi imagineaza ca exista gloante de argint care rezolva magic problemele?
In orice sistem real se mai fac si compromisuri, se mai duplica niste date, se mai ocoleste putin normalizarea aia purista, asta e realitatea.

P.S.: Gloantele de argint sunt pentru omorat vampiri si/sau varcolacii, nu se folosesc in dezvoltarea software.

Nu e dar aia e, e un trade-off.

Noi stocam datele in MSSQL si pe urma distribuim intr-un cluster de solr/elasticsearch datele sub un alt format ca sa fie mai usor de cautat/filtrat/…
Daca ar fi sa facem aceeasi arhitectura doar folosind MSSQL ar fi alte probleme.

Desigur avem probleme de genul, datele exista in MSSQL dar nu exista inca in Solr pentru ca inca nu s-au propagat (1-3 sec).

Ce e important e sa fie foarte clar unde se scriu datele si cum se propaga modificarile astfel incat sa fie totul sincronizat respectiiv sa fie o cale usoara daca necesita resincronizare.

Iar in unele situatii chiar trebuie sa duplici datele, de exemplu la informatii care intra pe o factura, datele de client nu vrei sa se modifice pe parcurs cand te uiti la factura aia peste ceva timp.

2 Likes

Nu se folosesc pentru ca nu exista, dar unii totusi cred ca exista, punand accent nemeritat pe functionalitatea ne-esentiala si imaginandu-si ca orice problema se rezolva prin folosirea de design patterns, ‘tehnologii’, biblioteci, inca un nivel de abstractizare, niste interfete frumoase pe-acolo, un api meseriash, eventual apeland in biblioteca bloatware .solve_my_problem().

Singura problema asa e ca visul de independenta a microserviciilor e doar un vis.
Si visul ala nu rezolva de fapt problema:

Wow, mie mi se pare ca voi vorbiti de lucruri pe care ar trebui sa le faca un departament intreg. Incerc sa citesc destul de mult articole si sa invat, dar parca nu e timp suficient pentru toate :)))

intrebarea mea a pornit fix de la gandul asta. Un microserviciu are baza lui de date, altfel nu mai e microserviciu. E fix un alt serviciu care se foloseste de un db comun. Poate un serviciu are nevoie de un MSSQL in spate, altul are nevoie de Mongo, fiecare e cu storageul lui, in functie de problemele pe care le rezolva.

Cred ca de cele mai multe ori se incalca lucrul asta din X motive, mai multe servicii folosesc aceeasi db. Duplicarea datelor intre db-uri nu mi s-a parut ok dpdv al unui junior, daca lucrez pe OrdersDb si gasesc acolo un userId, o sa am nevoie de explicatii sa imi dau seama ce e, de unde e si ce face campul respectiv.

Ca sa se evite folosirea aceleiasi db de mai multe “microservicii”, duplicarea datelor pare o solutie. userId din OrdersService e doar o mica informatie si are o insemnatate. Pentru a pastra o “uniformitate” intre servicii, userId din UsersService ma gandesc ca ar trebui sa reprezinte acelasi lucru. Mai ales cand cele 2 servicii sunt dezvoltate de aceeasi echipa. E drept, neavand legatura ordersDb cu usersDb, iti permiti ca “userId”-ului din users sa ii dai o alta insemnatate sau un alt format. Dar e asta corect? Pentru ca in tot ecosistemul asta, “userId”-ul trebuie sa reprezinte acelasi lucru, indiferent de serviciu.

Mi se pare ca “rezolvand” o problema, creezi o alta si … nu exista proiect fara compromisuri.

Aici intervine rolul unui arhitect experimentat care poate sa vina cu 3+ variante de solutie pentru problema data si sa aleaga cea optima pe baza experientei.
Varianta adoptam microservici peste tot din puncul meu de vedere ar trebui sa fie ultima pe lista si atent analizata si nu prima.

Si un lucru foarte important, nu lua pe bune tot ce citesti pe net. Orice informatie o treci prin filtrul propriu si iincerci sa intelegi avantaje/dezavantaje. Toate articolele care le vei gasi pe un subiect anume vor fi in general pozitive si nu mentioneaza dezavantajele.

5 Likes