Lecția 73 s-a închis pe modular monolith și pe ideea că pentru majoritatea echipelor forma implicită de backend e un singur deployable cu granițe interne puternice. Lecția asta presupune întrebarea de după: când chiar ai mai multe servicii (fie două, fie două sute), cum ar trebui să vorbească între ele? Răspunsul cu RPC sincron (serviciul A apelează serviciul B prin HTTP, așteaptă răspunsul, returnează către apelant) e implicitul care iese primul în producție și de multe ori cel care sună înapoi echipa peste șase luni cu o listă de moduri de eșec. Event-driven architecture e pattern-ul alternativ, iar industria a petrecut un deceniu învățând unde funcționează și unde nu.
Pe scurt: în event-driven architecture, serviciile nu se apelează între ele. Emit evenimente. Alte servicii se abonează și reacționează. Event bus-ul (adesea Kafka, adesea un echivalent gestionat) stă între ele și decuplează producătorii de consumatori.
Beneficiile sunt reale. Compromisurile sunt diferite față de RPC sincron, nu strict mai bune, iar alegerea între cele două moduri de a coordona workflow-uri cross-service (choreography și orchestration) e a doua decizie pe care echipa trebuie s-o ia odată ce s-a angajat la evenimente. Lecția asta acoperă pattern-ul, cele două variante, pattern-ul saga care se implementează adesea peste și setul de unelte din 2026 care a convergat în jurul lui.
RPC sincron și costul lui
Pornim de la modul de eșec împotriva căruia reacționează pattern-ul event-driven. Într-un backend cu RPC sincron, un request de la utilizator lovește serviciul A, care apelează serviciul B, care apelează serviciul C, care apelează serviciul D. Fiecare apel e un request HTTP sau gRPC. Fiecare apel blochează până când următorul serviciu răspunde. Întregul lanț de apeluri se ține deschis cât timp request-ul traversează sistemul.
Problemele se compun:
Latența se adună. Latența vizibilă utilizatorului e suma fiecărui hop. 50ms p95 la fiecare din patru servicii înseamnă 200ms p95 cap-coadă, înainte de rețea și de baza de date.
Tail latency domină. Dacă serviciul D are p99 de 500ms, iar request-ul tău se ramifică spre multe astfel de apeluri, p99-ul tău cap-coadă e mult mai rău decât p99-ul oricărui singur serviciu.
Cascade de eșec. Când serviciul D e jos, serviciul C dă timeout. Când serviciul C dă timeout, serviciul B dă timeout. Întregul lanț e jos pentru că veriga cea mai adâncă e jos. Circuit breakers și retry-urile ajută; nu elimină cuplarea.
Cuplare la deploy. Serviciul A știe adresa serviciului B și forma API-ului lui B. Când B se schimbă, A trebuie actualizat. Înmulțește cu N servicii.
Astea nu sunt bug-uri în RPC sincron; sunt consecințele modelului. Pattern-ul își are locul lui, iar majoritatea căilor de citire vizibile utilizatorului sunt mai ușor de raționat ca RPC-uri. Dar pentru căile de scriere și pentru workflow-urile cross-service, costul se adună.
Forma event-driven
Alternativa: serviciul A emite un eveniment („OrderPlaced”, cu detaliile comenzii în payload) și returnează către apelant. Serviciile B, C și D s-au abonat la evenimentele OrderPlaced. Fiecare consumă evenimentul și-și face treaba asincron. Request-ul vizibil utilizatorului se termină la A; restul se întâmplă în fundal.
Substratul e de obicei un log durabil: Kafka (lecția 42), Pulsar, AWS Kinesis, Google Pub/Sub, Azure Event Hubs. Producătorul scrie o singură dată. Mulți consumatori citesc independent. Log-ul persistă evenimentele astfel încât un consumator lent sau temporar jos să poată recupera mai târziu. Semanticile de livrare (lecția 16) dictează restul contractului.
Beneficiile inversează modurile de eșec ale RPC-ului sincron:
Latența pentru utilizator e doar latența serviciului A. Munca din aval se întâmplă în afara benzii.
Izolarea defecțiunilor. Dacă serviciul D e jos, e problema serviciului D. Serviciul A returnează către utilizator normal; evenimentele se așază în coadă în log; serviciul D recuperează când revine.
Cuplare slabă. Serviciul A nu știe cine consumă OrderPlaced. Consumatori noi pot fi adăugați fără să se schimbe A. Consumatori vechi pot fi eliminați fără să se schimbe A.
Auditabilitate. Log-ul de evenimente e o înregistrare a tot ce s-a întâmplat, în ordine. Replay-ul log-ului reconstruiește starea. Debugging-ul merge înapoi prin evenimente.
Compromisurile sunt și ele reale:
Eventual consistency. Starea sistemului devine consistentă în cele din urmă, nu imediat. Utilizatorul își vede comanda plasată; emailul de confirmare ajunge o secundă mai târziu. Pentru majoritatea workflow-urilor asta e ok. Pentru unele nu.
Debugging mai greu. „Ce se întâmplă când o comandă e plasată?” nu se mai poate răspunde citind codul serviciului A. Răspunsul e răspândit între toți consumatorii lui OrderPlaced.
Schema evolution. Schema evenimentului e acum un contract între servicii. Schimbarea ei e o schimbare coordonată.
Substrat operațional. Rularea unui cluster Kafka (sau a echivalentului gestionat) e propria sa disciplină operațională.
Pattern-ul merită costul atunci când workflow-ul e natural asincron, când munca din aval poate eșua și retry-a independent și când echipa e dispusă să investească în substrat. E exagerat pentru cazul simplu în care un request are nevoie doar de o scriere în baza de date și un răspuns.
Choreography: serviciile reacționează
Odată ce evenimentele sunt substratul, ai două moduri de a exprima un workflow în mai mulți pași. Primul e choreography. Fiecare serviciu își știe propria treabă. Când vede un eveniment care îl interesează, își face treaba și emite propriul eveniment. Nu există un coordonator central. Workflow-ul e comportamentul emergent al serviciilor care reacționează unele la altele.
Un exemplu canonic: un workflow de plasare de comandă.
sequenceDiagram
participant U as User
participant O as OrderService
participant K as Kafka
participant P as PaymentService
participant I as InventoryService
participant N as NotificationService
U->>O: POST /orders
O->>K: emit OrderPlaced
O-->>U: 201 Created
K->>P: OrderPlaced
P->>K: emit PaymentAuthorized
K->>I: PaymentAuthorized
I->>K: emit InventoryReserved
K->>N: InventoryReserved
N->>N: send confirmation email
Serviciul de comenzi nu știe nimic despre plăți, inventar sau notificări. Emite OrderPlaced și returnează. Serviciile din aval se abonează și reacționează. Fiecare serviciu e mic, focusat și deployable independent.
Punctele forte ale choreography:
Cuplare slabă. Adăugarea unui nou participant (analytics, fraud detection, notificări către parteneri) e o chestiune de a te abona la evenimentele relevante. Niciun serviciu existent nu se schimbă.
Niciun punct unic de eșec pentru workflow. Fiecare serviciu e independent.
Potrivire naturală pentru reacții cu adevărat independente. Când N servicii trebuie să reacționeze la același eveniment în paralel și nu depind unul de altul, choreography e forma evidentă.
Punctele slabe:
Nicio sursă unică de adevăr pentru workflow. A întreba „ce se întâmplă când o comandă e plasată” cere citirea codului fiecărui consumator.
Greu de văzut „unde e blocată comanda #123?”. Comanda e ce zic evenimentele că e. Debugging-ul unei comenzi blocate înseamnă citirea log-urilor mai multor servicii și reconstruirea stării din secvența de evenimente.
Dependențe implicite. Serviciul C depinde de B care emite PaymentAuthorized. Dacă B își schimbă numele evenimentului sau payload-ul, C se rupe. Dependența e reală dar invizibilă în cod.
Logica de compensare e împrăștiată. Când workflow-ul trebuie să facă rollback (plata autorizată dar inventarul indisponibil), compensarea trebuie coordonată între servicii care nu știu unul de altul.
Orchestration: un coordonator conduce
Alternativa e orchestration. Un coordonator central (un orchestrator, un saga manager, un workflow engine) ține definiția workflow-ului și spune fiecărui serviciu ce să facă, în ce ordine, cu tratarea explicită a eșecurilor și compensărilor.
Același workflow de plasare de comandă, ca flux orchestrat:
sequenceDiagram
participant U as User
participant O as OrderService
participant W as WorkflowEngine
participant P as PaymentService
participant I as InventoryService
participant N as NotificationService
U->>O: POST /orders
O->>W: start OrderWorkflow
O-->>U: 201 Created
W->>P: AuthorizePayment
P-->>W: success
W->>I: ReserveInventory
I-->>W: success
W->>N: SendConfirmation
N-->>W: success
W->>W: workflow complete
Workflow engine-ul deține starea workflow-ului. Știe pasul curent, pasul următor și ce să facă la eșec. Serviciile participante sunt pasive: expun acțiuni, iar orchestrator-ul le invocă.
Punctele forte ale orchestration:
Sursă unică de adevăr. Definiția workflow-ului e o singură bucată de cod sau configurație care surprinde întregul flux.
Vizibilitate. UI-ul orchestrator-ului arată unde e fiecare workflow. „Unde e blocată comanda #123?” e o interogare împotriva orchestrator-ului, nu un exercițiu de criminalistică.
Tratare explicită a erorilor și compensare. Definiția workflow-ului spune ce se întâmplă la eșec. Retry pasul ăsta. Compensează celălalt. Escalează la un om după trei eșecuri.
Raționament mai ușor pe workflow-uri complexe. Când workflow-ul are zece pași cu ramificări condiționale și timeout-uri, orchestration face structura vizibilă. Choreography o ascunde.
Punctele slabe:
Orchestrator-ul e un serviciu critic. Dacă cade, niciun workflow nu progresează. Deployment-urile de producție îl fac highly available, dar responsabilitatea operațională e reală.
Cuplare mai strânsă între orchestrator și participanți. Orchestrator-ul cunoaște participanții după nume și adresă. Adăugarea unui nou participant înseamnă schimbarea definiției workflow-ului.
O nouă bucată de infrastructură de rulat. Choreography are nevoie doar de event bus. Orchestration are nevoie și de orchestrator pe deasupra.
Alegerea între choreography și orchestration nu e absolută. Majoritatea sistemelor mature folosesc ambele: choreography pentru cazurile cu cuplare slabă (analytics, audit, notificări de tip broadcast), orchestration pentru workflow-urile de business complexe în mai mulți pași (procesare de comandă, onboarding de cont, soluționare de daune).
Pattern-ul saga
Pattern-ul saga e soluția canonică pentru problema pe care 2PC (lecția 15) o încearcă și o ratează la scară: cum coordonezi o tranzacție care se întinde peste mai multe servicii, fiecare cu propria bază de date, când distributed locking și 2PC sunt prea scumpe?
O saga exprimă tranzacția de durată ca o secvență de tranzacții locale, una per serviciu, cu o compensare definită pentru fiecare. Dacă pasul trei eșuează, saga rulează compensările pentru pașii unu și doi în ordine inversă, lăsând sistemul într-o stare consistentă (chiar dacă nu identică).
Saga în exemplul cu comanda:
- AuthorizePayment. Compensare: VoidAuthorization.
- ReserveInventory. Compensare: ReleaseReservation.
- SendConfirmation. (Nu e nevoie de compensare; un email în plus e inofensiv.)
Dacă ReserveInventory eșuează, saga rulează VoidAuthorization. Utilizatorul vede o respingere a comenzii; plata nu e debitată; sistemul e consistent.
Saga poate fi implementată în oricare dintre cele două moduri. Ca choreography: fiecare serviciu emite un eveniment de succes când pasul lui reușește și un eveniment de eșec când eșuează; serviciile din aval compensează când văd un eșec. Ca orchestration: orchestrator-ul rulează pașii în ordine și invocă explicit compensările la eșec.
Saga orchestrată e pattern-ul dominant în 2026 pentru că vizibilitatea orchestrator-ului face starea saga-ei debuggable, ceea ce e partea care doare cel mai mult în producție.
Setul de unelte din 2026
Tooling-ul de orchestration s-a consolidat. Cei patru contendenți serioși pentru orchestrare de workflow-uri definite în cod:
Temporal e alegerea dominantă pentru workflow-uri definite în cod. Workflow-urile sunt scrise ca cod obișnuit (Go, Java, TypeScript, Python) iar Temporal le face durabile: starea e checkpoint-ată automat, restart-urile reiau de la ultimul checkpoint, retry-urile și timeout-urile sunt first-class. Modelul mental e „scrii workflow-ul ca o funcție și Temporal se ocupă de eșecuri”. Temporal Cloud e oferta gestionată.
AWS Step Functions e răspunsul AWS-native. Workflow-urile sunt definite în Amazon States Language (un DSL JSON) și rulează în interiorul contului AWS. Integrarea strânsă cu restul AWS (Lambda, ECS, SQS, DynamoDB) e atracția principală. Compromisul e lock-in pe AWS.
Camunda e varianta business-process. Workflow-urile sunt definite în BPMN (Business Process Model and Notation), standardul XML din lumea proceselor de business. Publicul e echipele unde analiștii de business editează workflow-ul alături de ingineri. Camunda 8 (rescrierea cloud-native) a înlocuit Camunda 7 (bazat pe Activiti) începând cu 2022.
Argo Workflows e opțiunea Kubernetes-native. Workflow-urile sunt definite ca resurse custom de Kubernetes, fiecare pas e un pod, iar orchestrator-ul în sine e un controller Kubernetes. Potrivirea e pentru pipeline-uri de date și ML care rulează deja pe Kubernetes; cadrul din lecția 57 a acoperit Argo din unghi de orchestrare de date.
Alegerea cade de obicei din infrastructura existentă a echipei. Magazin AWS greu cu ingineri confortabili în JSON: Step Functions. Echipă greenfield care vrea ca logica workflow-ului să fie cod obișnuit: Temporal. Enterprise cu tradiții BPMN: Camunda. Echipă de date Kubernetes-native: Argo.
Trimiteri încrucișate și pasul următor
Pattern-ul se conectează cu mai multe lecții anterioare. Lecția 15 a introdus 2PC și saga ca alternativă pentru consistență cross-service. Lecția 16 a stabilit semanticile de livrare pe care event bus-ul trebuie să le onoreze. Lecția 42 a mers în adâncime pe Kafka, cea mai comună coloană vertebrală pentru sistemele event-driven. Lecția 57 a acoperit orchestrator-ele din unghi de pipeline de date; lecția asta e același cadru aplicat workflow-urilor de business cross-service.
Lecția 75 închide deschiderea modulului trăgând cu un strat mai în afară: odată ce serviciile și workflow-urile lor sunt așezate, unde rulează ele, geografic? Deployment-urile multi-region întorc aceleași întrebări arhitecturale pe-o parte, iar modurile de eșec ale replicării cross-region sunt următorul lucru la care echipa trebuie să se gândească.
Citations
- Temporal documentation,
https://docs.temporal.io/, retrieved 2026-05-01. The dominant code-driven workflow engine. - AWS Step Functions documentation,
https://docs.aws.amazon.com/step-functions/, retrieved 2026-05-01. - Camunda 8 documentation,
https://docs.camunda.io/, retrieved 2026-05-01. - Argo Workflows documentation,
https://argo-workflows.readthedocs.io/, retrieved 2026-05-01. - Chris Richardson, “Microservices Patterns”, Manning, 2018. Standard reference for the saga pattern, choreography vs orchestration, and event-driven design.
- Sam Newman, “Building Microservices, 2nd Edition”, O’Reilly, 2021. Chapter on inter-service communication.
- Hector Garcia-Molina and Kenneth Salem, “Sagas”, ACM SIGMOD 1987. The original saga paper.