Arhitectura datelor și a sistemelor, de la zero Lecția 1 / 80

Ce este de fapt arhitectura software

Definiții care nu sunt inutile. Arhitectura ca setul de decizii care sunt scumpe de schimbat mai târziu. Regula 'dacă e greu de schimbat, e arhitectură'.

Bun venit la lecția unu din cursul de Arhitectură de Date și Sisteme. Optzeci de lecții, plecând de la un singur VM care rulează un script Python și ajungând la un sistem global multi-region care nu cade când unul dintre data centerele lui ia foc. Vom trece prin mijlocul dezordonat în ordine: cum crește un sistem de la o cutie la două, de la două la o flotă mică, de la o flotă mică la o regiune și de la o regiune la întreaga planetă. Vom acoperi trade-off-urile pe drum, lucrurile pe care nu le mai poți „de-decide” odată ce le-ai decis și numărul surprinzător de mare de eșecuri care vin din faptul că oamenii uită că rețeaua nu este, de fapt, fiabilă.

Cursul ăsta se sprijină puternic pe trei texte pe care cred că orice persoană serioasă din domeniul sistemelor ar trebui să le citească: „Designing Data-Intensive Applications” de Martin Kleppmann, „Building Microservices” de Sam Newman și Google SRE workbook. Niciuna dintre ele nu e reprodusă aici. Împrumut vocabular din ele, exemple ocazionale și gustul general de cum să gândești despre sistemele distribuite, dar lecțiile sunt scrise de la zero, cu exemplele fir-roșu și unghiul pe care eu îl găsesc util. Dacă termini cursul și vrei să mergi mai în adâncime, alea trei sunt locul unde te duci.

Înainte să atingem un singur diagram de arhitectură, trebuie să răspundem la o întrebare care sună suspect de mult ca filosofia de licență și care se dovedește a conta efectiv când ești într-o ședință: ce este arhitectura software, mai exact? Merită să o clarificăm pentru că definiția greșită te va face să te cerți despre lucruri greșite tot restul carierei.

Definițiile clasice și de ce nu te ajută

Cea mai citată definiție de manual vine de la Bass, Clements și Kazman în „Software Architecture in Practice”:

Arhitectura software a unui sistem este setul de structuri necesare pentru a raționa despre sistem, care cuprinde elemente software, relații între ele și proprietăți ale ambelor.

Asta e o propoziție perfect corectă. E și, ca s-o spun pe șleau, inutilă când stai în fața unei tablei la 4 după-amiaza într-o marți încercând să decizi dacă să folosești Postgres sau DynamoDB. Îți spune că arhitectura înseamnă „structuri și elemente și relații și proprietăți”, ceea ce e cam ca și cum ai spune că o masă înseamnă „ingrediente și combinații și arome și prezentări”. Tehnic adevărat. Nu te ajută să gătești cina.

Martin Fowler, care s-a gândit la asta mai mult decât majoritatea, a renunțat să găsească o definiție curată și a citat în schimb pe Ralph Johnson:

Arhitectura e despre lucrurile importante. Oricare ar fi acelea.

Asta e mai aproape de adevăr și e și evident circular. Ce e important? Important pentru cine? Când? Ideea lui Fowler citând asta este că arhitecții pe un proiect real ajung să fie de acord, mai ales intuitiv, asupra a ceea ce sunt „lucrurile importante” și acolo își petrec timpul. Definiția e o descriere a ceea ce arhitecții fac efectiv, nu o procedură pentru a-ți da seama ce să faci.

Am citit fiecare variație a acestei definiții care există și toate au o singură problemă comună. Descriu arhitectura ca pe un lucru (un set de structuri, un set de decizii, un set de lucruri importante) fără să-ți spună cum să recunoști dacă o anumită alegere pe care urmează să o faci este arhitecturală sau nu. Și recunoașterea asta e toată abilitatea. Dacă poți identifica decizia arhitecturală într-o grămadă de alegeri, știi pe care să încetinești, pe care să le dezbați, pe care să le scrii și pe care doar să le livrezi.

Așa că iată definiția de lucru pe care o voi folosi pentru următoarele optzeci de lecții.

Definiția de lucru: arhitectura este ce e scump de schimbat

Arhitectura este setul de decizii care sunt scumpe de schimbat mai târziu.

Asta e tot. Dacă o decizie e greu de schimbat odată ajunsă în producție, e arhitecturală. Dacă e ușor de schimbat, nu e.

„Lucrurile importante” ale lui Ralph Johnson sunt doar un mod mai puțin precis de a spune același lucru. Motivul pentru care o decizie e „importantă” în sensul arhitectural este că anularea ei te-ar costa săptămâni, luni sau ani. Deciziile care nu au proprietatea asta nu sunt arhitecturale, indiferent cât de grele par în moment. Alegerea convenției de denumire a variabilelor pare grea într-un code review și nu e arhitecturală. Alegerea limbajului de programare pentru un serviciu pare de rutină în prima săptămână și absolut este.

Definiția asta are un corolar util: deciziile arhitecturale nu sunt același lucru cu deciziile bune de inginerie și nu sunt întotdeauna același lucru cu deciziile pe care le dezbați cel mai mult. Unele decizii arhitecturale se iau în cinci secunde pentru că răspunsul e evident („o să folosim Postgres pentru că toți de aici știu Postgres”). Unele decizii non-arhitecturale consumă o săptămână de dezbateri („care ar trebui să fie convenția de denumire a câmpurilor JSON”). Cantitatea de dezbatere nu e un semnal fiabil. Costul anulării este.

Alegeri arhitecturale versus alegeri de design

Hai să facem asta concret. Iată o listă de alegeri pe care o echipă le-ar putea face într-un proiect tipic. Unele sunt arhitecturale în sensul nostru, altele nu. Treci prin ele cu testul „scump de schimbat?” în minte.

  • Alegerea bazei de date. Arhitecturală. Trecerea de la Postgres la DynamoDB după trei ani e un proiect de mai multe luni. Vei rescrie data access layer-ul, vei reproiecta schemele, vei reface indexurile, vei schimba pattern-urile tranzacționale, vei reantrenează pe toți și vei rerula testele de performanță. Oamenii fac asta și își amintesc pentru totdeauna.

  • Alegerea limbajului de programare pentru un serviciu. Arhitecturală. Rescrierea unui serviciu Java de 50.000 de linii în Go nu e ceva ce faci într-un weekend. Chiar și rescrierea graduală cu un strangler pattern e un an din viața cuiva.

  • HTTP sincron versus event bus pentru comunicare între servicii. Arhitecturală. Toată forma în care serviciile interacționează, eșuează, fac retry și se observă reciproc depinde de asta. Schimbarea unuia cu altul atinge fiecare endpoint din sistem.

  • Deployment single-region versus multi-region. Extrem de arhitecturală. Presupunerile despre latență, modelul de consistență, modul în care gestionezi failover-ul și modul în care plătești cloud provider-ul, toate se schimbă.

  • Alegerea REST versus GraphQL pentru un API public. În mare parte arhitecturală, pentru că odată ce clienții externi depind de el, deprecierea durează ani de email-uri politicoase.

  • Numele unei metode într-o clasă internă. Non-arhitecturală. Redenumește, rulează testele, livrează.

  • Convenția de denumire pentru variabilele de mediu. Non-arhitecturală. Enervant de schimbat dar ieftin.

  • Dacă să folosești un framework de logging sau instrucțiuni print în prototiparea timpurie. Non-arhitecturală. Vei trece la un framework în ziua în care începi să rulezi în staging.

  • Alegerea provider-ului de auth (Auth0 versus Cognito versus Keycloak versus să-ți construiești propriul). Arhitecturală. Migrarea utilizatorilor între sisteme de auth e un proiect real, parțial pentru că nu poți migra hash-urile de parole între provideri fără să forțezi fiecare utilizator să-și reseteze parola.

  • Politica exactă de retry și backoff pentru un singur apel HTTP. Aproape niciodată arhitecturală. Ajustează marți, deploy miercuri, vezi cum funcționează mai bine.

Pattern-ul: orice atinge schema, contract, protocol, limbaj, topologie de deployment, identitate sau ownership de date tinde să fie arhitectural. Orice atinge nume, formate, helper-i interni sau algoritmi locali de obicei nu e. Există excepții. Lista e un punct de plecare, nu un checklist.

Scara ireversibilității deciziilor

Tăietura „scump versus ieftin de schimbat” e o sită utilă la prima trecere, dar în practică costul-de-anulare e un continuum, nu un binar. Mi se pare util să-l gândesc ca pe o scară cu cel puțin trei trepte:

Reversibil într-o zi. Un nou feature flag. O schimbare de logging level. Un index SQL. O variabilă redenumită. O versiune de bibliotecă bumpată, presupunând că nimic nu se rupe. Poți livra schimbarea dimineața și o poți da înapoi după-amiaza dacă merge prost.

Reversibil într-un trimestru. Un nou serviciu intern. Un swap între o tehnologie de cache și alta. O mutare dintr-o regiune cloud în alta în cadrul aceluiași provider. O trecere de la un ORM la altul, într-o bază de cod suficient de mică încât „rescrie data layer-ul” e fezabil într-un trimestru. Aceste schimbări au nevoie de un plan de proiect și cel puțin un inginer care lucrează la ele câteva săptămâni. Sunt date înapoi, doar nu sunt date înapoi cu ușurință.

Reversibil într-un an, sau niciodată. Alegerea familiei de bază de date. Alegerea cloud provider-ului. Alegerea sistemului de autentificare. Alegerea formei API-ului public. Alegerea topologiei de deployment geografice. Alegerea granițelor de ownership de date între echipe. Odată ce astea sunt în producție cu o bază de clienți peste ele, ieșirea e un efort major al companiei. Unele nu sunt nici măcar tehnic reversibile fără pierdere de date sau ruperea contractelor; doar trăiești cu ele și le ocolești.

Abilitatea arhitecturii e să recunoști pe ce treaptă e o decizie și să potrivești seriozitatea procesului tău cu treapta. O decizie de nivel-zi poate fi luată de un singur inginer în cincisprezece minute. O decizie de nivel-trimestru merită un architecture decision record (ADR) și câteva ore de design. O decizie de nivel-an merită cercetare reală, prototipare reală și un mic panel de oameni care au făcut alegeri similare anterior care să fie de acord că e alegerea corectă.

Greșeala clasică a echipelor de juniori e să trateze fiecare alegere ca o decizie de nivel-an și să nu livreze niciodată nimic. Greșeala clasică a echipelor senior sub presiunea deadline-ului e să trateze deciziile de nivel-an ca pe unele de nivel-trimestru pentru că nu par mare lucru în moment. Ambele sunt rele. Treaba e să știi care e care.

flowchart LR
    A[Easy to change<br/>hours to days] --> B[Medium<br/>weeks to a quarter]
    B --> C[Hard<br/>quarter-plus to never]
    A1[variable name<br/>log level<br/>retry policy<br/>feature flag] -.examples.-> A
    B1[new internal service<br/>cache swap<br/>region within cloud<br/>ORM change] -.examples.-> B
    C1[database family<br/>cloud provider<br/>auth provider<br/>public API shape<br/>multi-region topology] -.examples.-> C

Treapta din mijloc e interesantă pentru că acolo trăiește majoritatea muncii efective de „arhitectură”. Deciziile de nivel-an sunt rare; faci câteva pe sistem, în total. Deciziile de nivel-zi sunt constante și nu au nevoie de ceremonie. Deciziile de nivel-trimestru sunt cele unde un proces deliberat își merită banii, pentru că se întâmplă suficient de des încât cele proaste se acumulează și sunt suficient de scumpe încât nu-ți permiți să le faci neglijent.

Ce acoperă cursul ăsta și ce nu

Cele optzeci de lecții sunt organizate în zece module. La o privire:

  1. Foundations (lecțiile 1 până la 8). Definiții, cerințe, modelul C4, vocabularul de bază. Ești în lecția 1 chiar acum.
  2. O singură mașină (lecțiile 9 până la 16). Ce poți face pe un singur VM. Modelul de proces, threading, async, file I/O, OS-ul local ca sistem.
  3. Storage și baze de date (lecțiile 17 până la 24). Relațional versus document versus key-value versus columnar. Indexuri, tranzacții, niveluri de izolare.
  4. Două mașini și rețeaua (lecțiile 25 până la 32). Latență, lățime de bandă, fallacies of distributed computing, RPC versus REST versus gRPC, idempotență.
  5. Caching, cozi și async (lecțiile 33 până la 40). Redis, Kafka, message brokers, eventual consistency, outbox pattern.
  6. Decompoziția în servicii (lecțiile 41 până la 48). Monolituri versus monolituri modulare versus microservicii. Când să le împarți, când nu, cum să trasezi cusăturile.
  7. Reliability și observability (lecțiile 49 până la 56). SLO-uri, error budgets, logging, metrici, tracing, on-call.
  8. Scaling out (lecțiile 57 până la 64). Sharding, partiționare, consistent hashing, leader election, consens distribuit la o adâncime suficientă.
  9. Sisteme multi-region și globale (lecțiile 65 până la 72). Active-passive versus active-active, geo-routing, rezolvare de conflicte, costul real al globalizării.
  10. Practică și luare de decizii (lecțiile 73 până la 80). ADR-uri, fitness functions, evolutionary architecture, interviuri și cum să-ți dai seama când supra-inginerești.

Diagramă de creat: O diagramă cu 10 căsuțe aranjate într-o grilă 5x2, fiecare căsuță etichetată cu un număr de modul și un cluster de subiecte. Modulul 1 (stânga sus, Foundations) și Modulul 10 (dreapta jos, Practice) colorate diferit față de cele opt module din mijloc, ca să marcheze că sunt capete de carte. Săgeți de la stânga la dreapta și de sus în jos arătând ordinea naturală de citire. Sub fiecare căsuță, două sau trei subteme cu text mai mic (de exemplu sub „Storage și baze de date” listează „relational, document, columnar”). Titlu sus: „Data and System Architecture: 80 lessons in 10 modules.”

Ce nu este cursul ăsta: un tutorial pe un cloud provider specific, un deep dive într-un singur limbaj de programare sau un manifest pentru un singur stil arhitectural. Voi menționa AWS, Azure, GCP și proiectele open-source majore pe parcurs, dar scopul e ca tu să poți raționa despre sisteme indiferent de ce butoane se întâmplă să apeși anul ăsta. Cloud provider-ii își vor redenumi produsele de încă trei ori între momentul în care scriu asta și momentul în care citești tu. Principiile, nu.

Cu ce ar trebui să rămâi din lecția asta

O propoziție: arhitectura este setul de decizii care sunt scumpe de schimbat mai târziu.

Două corolare: volumul de dezbatere nu e un semnal fiabil pentru cât de arhitecturală e o decizie, iar costul-de-anulare trăiește pe o scară, nu pe un binar.

Un obicei: când ești pe cale să iei o decizie tehnică, ia-ți cinci secunde să te întrebi „dacă mă înșel cu asta, cât costă să o anulez?” Dacă răspunsul e „o după-amiază”, mergi rapid. Dacă răspunsul e „un trimestru”, încetinește și scrie. Dacă răspunsul e „un an”, cheamă întăriri.

Lecția următoare abordează cealaltă jumătate a input-ului arhitectural: cerințele. Mai exact, diferența dintre cerințele funcționale (ce face sistemul) și cerințele non-funcționale (cât de bine face), și de ce a doua categorie e cea care de fapt conduce arhitectura. Ne vedem acolo.

Referințe

  • Bass, Clements, Kazman. Software Architecture in Practice, ediția a 4-a (2021).
  • Martin Fowler. Patterns of Enterprise Application Architecture (2002), și eseurile lui în desfășurare la martinfowler.com.
  • Simon Brown. The C4 model, https://c4model.com (consultat 2026-05-01). Acoperit în lecția 3.
  • Martin Kleppmann. Designing Data-Intensive Applications (2017).
  • Sam Newman. Building Microservices, ediția a 2-a (2021).
  • Beyer, Jones, Petoff, Murphy. Site Reliability Engineering și The Site Reliability Workbook (Google, 2016 și 2018).
Caută