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

Trunk-based development: de ce au convers aici majoritatea echipelor moderne

Branch-uri scurte, feature flags, integrare continuă. Pattern-ul pe care Google, Facebook și Microsoft l-au adoptat la scară mare și ce cere ca să funcționeze.

Lecția 49 a introdus cele trei strategii de branching care reprezintă aproape toată practica profesională. Gitflow cu release branches formale, GitHub flow cu feature branches scurte și trunk-based development unde toată lumea face commit direct în main iar munca în desfășurare se ascunde în spatele feature flags. Lecția a notat că trunk-based e pattern-ul spre care converg cele mai multe echipe moderne pe măsură ce cresc și e pattern-ul pe care Google, Facebook și Microsoft îl rulează public la cele mai mari scări. Lecția aceasta explică de ce.

Argumentul nu e că trunk-based e universal corect. Are precondiții. Are nevoie de feature flags. Are nevoie de CI puternic. Cere o schimbare culturală în felul în care dezvoltatorii se gândesc la „gata”. Ideea e că odată ce precondițiile există, trunk-based e remarcabil de bine aliniat cu felul în care software-ul se construiește efectiv la viteză, iar alternativele încep să pară overhead în loc de siguranță. Înțelegerea pattern-ului în profunzime e utilă chiar și dacă echipa alege GitHub flow, pentru că orice deplasare în direcția GitHub flow (branch-uri mai scurte, mai multe feature flags, CI mai rapid) e o deplasare spre trunk-based, iar recunoașterea acestui lucru ajută echipa să facă îmbunătățiri incrementale fără să se angajeze la o migrare completă.

Pattern-ul într-un singur paragraf

Fiecare commit ajunge în main în câteva ore. Branch-urile încă există, dar doar pe durata code review-ului, iar review-ul e pe schimbări mici (zeci până la câteva sute de linii, nu mii). Testele rulează la fiecare push și la fiecare merge. CI e suficient de rapid încât un developer să facă push la o schimbare și să vadă verde în câteva minute. Codul care nu e încă gata pentru utilizatori e tot pe main, dar e împachetat într-un feature flag: un comutator runtime care controlează dacă codul rulează sau nu. Flag-ul stă oprit până când feature-ul e terminat, testat și gata pentru release. Apoi flag-ul se aprinde, în producție, adesea gradual. Release-ul devine apăsarea unui flag, nu deploy-ul unui build.

Ăsta e tot pattern-ul. Profunzimea e în precondiții și în schimbarea culturală.

De ce au convers aici echipele mari

Trei forțe împing organizațiile spre trunk-based pe măsură ce cresc.

Evitarea conflictelor domină la scară. Într-un monorepo cu 200 de ingineri, costul conflictelor de merge nu e teoretic. Branch-urile vechi de două săptămâni intră în conflict cu mii de commit-uri de schimbări, iar rezolvarea conflictului e periculoasă: dezvoltatorul care rescrie merge-ul are context limitat asupra a ce au făcut celelalte mii de commit-uri. Branch-urile vechi de câteva ore abia intră în conflict, pentru că suprafața de cod schimbat în câteva ore e mică. O echipă care lucrează simultan pe sute de branch-uri, fiecare trăind o săptămână, va petrece mai mult timp de inginerie pe rezolvarea de merge decât pe munca în sine. Branch-urile de câteva ore fac acest cost să dispară.

CI scalează cu commit-urile, nu cu branch-urile. Throughput-ul unui sistem CI se măsoară în builds per unitate de timp. O echipă care livrează 100 de commit-uri mici în main pe zi îi cere lui CI să ruleze 100 de build-uri, fiecare pe o schimbare mică. O echipă care livrează 10 branch-uri de durată lungă pe zi, fiecare cu săptămâni de muncă acumulată, îi cere lui CI tot să ruleze build-uri, dar fiecare build e pe o suprafață mai mare, durează mai mult, eșuează mai des și produce semnal mai puțin acționabil când eșuează. Trunk-based development produce natural pattern-ul de workload pe care CI îl gestionează cel mai bine: multe schimbări mici, fiecare individual tractabilă.

Continuous deployment devine posibil. Când fiecare commit pe main e gata de integrare, deploy pipeline-ul poate fi declanșat la fiecare merge fără intervenție umană. Unele organizații fac asta literal; altele fac batch deploys orare sau zilnice, dar tot tratează fiecare commit din main ca eligibil pentru deploy. Alegerea între batch-versus-continuous-deploy devine operațională în loc de arhitecturală, pentru că arhitectura (fiecare commit e releasable) suportă oricare variantă.

Combinația e ce face pattern-ul să funcționeze la scară. Conflictele dispar pentru că branch-urile sunt scurte. CI gestionează bine schimbările mici, deci feedback-ul e rapid. Continuous deployment devine de rutină pentru că fiecare commit e gata. Volanta se autoîntărește: feedback mai rapid încurajează schimbări mai mici, schimbările mai mici reduc și mai mult conflictele, mai puține conflicte îi încurajează pe dezvoltatori să facă commit chiar mai des.

Precondițiile

Pattern-ul funcționează doar dacă patru piese sunt la locul lor. Sari peste oricare dintre ele și trunk-based devine dureros sau periculos.

Feature flags la scară. Un feature flag e un comutator runtime care controlează dacă codul rulează. Implementarea cea mai simplă e o valoare de configurație citită la momentul request-ului: if config.features.new_checkout: handle_with_new_logic() else: handle_with_old_logic(). Implementările de nivel producție sunt mai bogate: targeting per utilizator, rollout-uri pe procente, kill switches, activări programate, audit logs cu cine a apăsat ce când. Ofertele comerciale includ LaunchDarkly, Unleash, ConfigCat, GrowthBook și Statsig. Efortul de standardizare e OpenFeature (https://openfeature.dev/, consultat 2026-05-01), care e un proiect CNCF ce oferă un SDK neutru de furnizor astfel încât echipele să poată schimba providerii de flags fără să rescrie codul aplicației. Multe echipe construiesc un sistem propriu, mai ales în organizațiile mari unde cerințele sunt specifice.

Capabilitatea care contează e una dinamică. Codul ajuns în main se livrează, dar flag-ul stă oprit până când echipa e gata. Flag-ul poate fi pornit întâi pentru un procent mic de utilizatori, monitorizat, extins și dat rollback instantaneu dacă ceva merge prost. Acesta e mecanismul care face „fiecare commit deploy-ează” să fie sigur, pentru că deploy-ul nu e release-ul: apăsarea flag-ului e release-ul.

CI puternic. Fiecare push pe un branch și fiecare merge în main rulează suita de teste. Bara e înaltă: pipeline-ul CI trebuie să fie suficient de rapid încât dezvoltatorii să aștepte minute, nu ore, pentru feedback, și suficient de fiabil încât un build roșu să însemne un eșec real, nu un flake. O echipă care lasă build-uri roșii să trăiască pe main a pierdut proprietatea care face trunk-based să fie sigur, pentru că dintr-odată nimeni nu știe ce commit-uri au stricat lucrurile, iar linia de integrare încetează să fie o fundație stabilă.

Practicile mecanice care susțin asta. CI obligatoriu pre-merge: niciun commit nu aterizează pe main fără un build verde pe schimbarea propusă. Branch protection rules în GitHub sau GitLab impun asta fără să te bazezi pe disciplina dezvoltatorilor. Paralelism de testare astfel încât suita să termine în minute chiar și pe măsură ce crește. Eliminare agresivă a flake-urilor, pentru că testele instabile îi învață pe dezvoltatori să ignore eșecurile CI, ceea ce distruge linia de integrare.

Code review pe schimbări mici. Pull requests sunt vechi de ore, nu de săptămâni. Diff-ul e suficient de mic încât un reviewer să-l poată citi în cincisprezece minute. Autorul nu a investit două săptămâni de muncă în el, deci feedback-ul e ieftin de aplicat. Aceasta e o proprietate culturală la fel de mult ca una tehnică: echipa trebuie să interiorizeze că PR-urile mici și frecvente sunt mai bune decât cele mari și rare, iar tooling-ul trebuie să le suporte. GitHub, GitLab, Gerrit și Phabricator au toate idiomuri pentru PR-uri înlănțuite sau stivuite care permit unui developer să împartă o schimbare logic mare într-o secvență de bucăți mici, reviewable.

Infrastructură de testare care prinde problemele la momentul PR. Suita de teste nu e doar unit tests. Include integration tests contra unor medii efemere, contract tests între servicii, suite de regresie pentru moduri de eșec cunoscute și (pentru munca cu date) tipurile de teste la nivel de pipeline pe care le acoperă lecția 51. Investiția e semnificativă. Câștigul e că problemele apar pe PR în loc de pe main, ceea ce înseamnă că au costul de câteva minute de reparat, în loc de un incident de producție de recuperat.

Schimbarea culturală

Precondițiile tehnice sunt partea mai ușoară. Partea mai grea e schimbarea culturală în felul în care dezvoltatorii se gândesc la „gata”.

În gitflow sau GitHub flow cu branch-uri lungi, modelul mental al dezvoltatorului e „o să fac merge când e gata”. Branch-ul e workspace privat; merge-ul e momentul în care munca devine publică. Branch-ul poate să stea privat atâta timp cât durează munca. Greșelile sunt recuperabile pe branch, pentru că nimic nu a fost încă integrat.

În trunk-based, modelul mental al dezvoltatorului e „o să aterizez chestia asta în întuneric și o termin pe main”. Munca devine publică la primul commit. Primul commit e mic (o funcție stub, un flag default off, un tabel în care încă nu scrie nimic) și ajunge în main. Commit-urile ulterioare conturează implementarea. Flag-ul stă oprit tot timpul, deci utilizatorii nu văd nicio schimbare de comportament, dar codul e pe main, integrându-se cu tot restul, beneficiind de CI și review la fiecare increment.

Schimbarea e incomodă pentru dezvoltatorii obișnuiți cu modelul mai vechi. Apar trei obiecții specifice.

„Și dacă codul meu nu e gata pentru producție?” Trunk-based nu cere ca codul să fie gata pentru producție. Cere ca codul să fie sigur de deployat. O funcție care există, e testată și e neapelabilă (pentru că nu o cheamă nimic, sau pentru că flag-ul e oprit) e sigură de deployat. „Gata pentru utilizatori” e decuplată de deploy.

„Și dacă codul meu e pe jumătate făcut?” O funcție pe jumătate făcută tot se face merge dacă are un test și un flag. Testul verifică că funcția pe jumătate făcută se comportă așa cum susține în prezent că se comportă. Flag-ul asigură că nu e folosită. Următorul PR adaugă mai multă funcționalitate, mai multe teste, iar flag-ul stă oprit până când totul funcționează. Fiecare PR e mic, individual mergeable și individual sigur.

„Și schimbările arhitecturale mari?” Pattern-ul e strategia de implementare paralelă. Arhitectura nouă e construită lângă cea veche. Ambele se livrează în producție. Flag-ul controlează care rulează pentru care utilizatori. Migrarea e un flip gradual al flag-ului de la vechi la nou, cu opțiunea de a face flip înapoi dacă ceva merge prost. Codul vechi se șterge doar când migrarea e completă și stabilă. E mai multă muncă decât o rescriere dintr-o singură lovitură, e dramatic mai sigur și e pattern-ul dominant pentru migrări la organizațiile mari de inginerie tocmai pentru că proprietățile de siguranță contează la acea scară.

flowchart TB
    subgraph build ["Build phase: small commits to main"]
        direction LR
        c1["init"] --> c2["flag<br/>stub off"] --> c3["small<br/>fix"] --> c4["step 1"] --> c5["step 2"] --> c6["step 3"] --> c7["tests"]
    end

    subgraph rollout ["Rollout phase: flip the flag"]
        direction LR
        c8["flag on<br/>5%"] --> c9["flag on<br/>50%"] --> c10["flag on<br/>100%"] --> c11["remove<br/>flag"]
    end

    build --> rollout
    rollout --> next["next feature, same loop"]

    classDef commit fill:#0d9488,stroke:#0d9488,color:#ffffff
    classDef flag fill:#fff5e6,stroke:#c89200,color:#5a3e00
    classDef boundary fill:transparent,stroke:#0d9488,stroke-dasharray: 5 5
    class c1,c2,c3,c4,c5,c6,c7,next commit
    class c8,c9,c10,c11 flag
    class build,rollout boundary

Diagram to create: a polished gitGraph of trunk-based with continuous tiny commits to main. Each commit is small. The “flag on” sequence shows the gradual rollout: 5%, 50%, 100%, remove flag. The visual point is that there are no branches in the diagram, the work is a sequence of small commits, and the release is a series of flag flips rather than a deploy.

Studiile de caz

Credibilitatea pattern-ului vine parțial din studiile de caz publice. Monorepo-ul Google cu mii de ingineri care fac commit într-un singur repository, descris în lucrarea ACM din 2016 a lui Rachel Potvin și Josh Levenberg „Why Google Stores Billions of Lines of Code in a Single Repository”, e exemplul canonic: trunk-based development la cea mai mare scară documentată vreodată public. Monorepo-ul Facebook e similar ca formă și a fost descris pe blog-ul de inginerie Facebook în perioada 2014-2018. Mutarea Microsoft a codebase-ului Azure DevOps la trunk-based a fost documentată în detaliu într-o serie de postări din 2018 pe blog-ul de inginerie al companiei.

Exemple la scară medie includ deployment pipeline-ul Etsy (făcut faimos prin blog-ul de inginerie Etsy și prin prezentările lui John Allspaw și Ian Malpass între 2010 și 2015) și practicile documentate public ale Spotify, Booking.com și ale multor altora. Pattern-ul nu e exotic; e linia de bază operațională la majoritatea organizațiilor de inginerie cu mai mult de câteva sute de ingineri și la multe altele mai mici.

Site-ul trunk-based (https://trunkbaseddevelopment.com, consultat 2026-05-01) menține o listă mai lungă de studii de caz și un corp substanțial de îndrumare practică. Cartea „Accelerate” a lui Forsgren, Humble și Kim (IT Revolution, 2018) se bazează pe cercetarea State of DevOps și identifică trunk-based development ca una dintre practicile asociate statistic cu organizațiile de inginerie cu performanță înaltă. Corelația nu dovedește cauzalitatea, dar dovezile sunt suficient de consistente încât apărătorii pattern-ului să-l trateze ca stabilit.

Când trunk-based nu se potrivește

Pattern-ul nu e universal. Trei populații sunt mai bine servite de altceva.

Echipe mici fără infrastructură de feature flags. O echipă de patru oameni care lucrează la o unealtă internă nu are nevoie de overhead-ul de feature flags, medii efemere și implementări paralele. Complexitatea trunk-based depășește beneficiul la această scară. GitHub flow cu branch-uri de o zi duce echipa aproape de același loc, cu mult mai puțină investiție în infrastructură.

Produse cu release-uri versionate. Aplicațiile mobile, bibliotecile, software-ul on-prem și firmware-ul embedded au toate un proces de release unde „ce am livrat” e semnificativ diferit de „la ce lucrăm”. Modelul de branching gitflow surprinde explicit acea diferență. Trunk-based poate fi făcut să funcționeze pentru aceste produse (mai ales cu feature flags), dar potrivirea naturală e gitflow, iar echipa ar trebui să reziste tentației de a folosi pattern-ul mai la modă de dragul faptului că e la modă.

Medii reglementate unde fiecare release e explicit. Dispozitivele medicale, sistemele core bancare, software-ul aerospațial și alte industrii cu aprobare formală reglementată pentru fiecare release nu pot deploya la fiecare commit. Reglementatorul vrea să știe ce e în acest release, semnat de cine, validat contra cărei suite de teste. Trunk-based development e incompatibil cu această cerință, pentru că cadența e greșită: nu există „acest release”, există doar „ce e pe main acum”. Pattern-ul care se potrivește aici e mai aproape de gitflow cu release branches formale, sign-off-uri formale și un deploy pipeline care include review-ul reglementator ca poartă.

Unghiul data-pipeline

Data pipelines pot folosi trunk-based development dacă infrastructura de integration testing (lecția 51) e suficient de bună și jobs sunt suficient de idempotente (lecția 38) ca să gestioneze rerularea pe date proaste.

Principiul e același ca pentru codul de serviciu: schimbarea ajunge în main, rulează în producție în spatele unui flag și e validată contra datelor reale înainte de a fi promovată. Pentru pipelines de batch asta înseamnă de obicei să scrii noua transformare alături de cea veche, într-un tabel de output separat, și să compari cele două output-uri înainte de a face cut-over. Pentru pipelines de streaming înseamnă să rulezi atât procesatorul vechi cât și pe cel nou contra aceluiași stream de input și să compari output-urile lor. Oricum ar fi, rolul flag-ului e același ca într-un serviciu: decuplează deploy-ul de release, astfel încât echipa să poată deploya frecvent fără să se angajeze de fiecare dată la o schimbare de comportament.

Lecția următoare intră în profunzime pe infrastructura de testare care face oricare dintre aceste lucruri sigure pentru munca cu date, pentru că trunk-based development fără încredere în teste e doar haos cu un nume mai elegant.

Citări și lecturi suplimentare

  • Paul Hammant și colaboratori, „Trunk-Based Development”, https://trunkbaseddevelopment.com (consultat 2026-05-01). Site-ul de referință, inclusiv îndrumare practică detaliată și studii de caz.
  • Rachel Potvin și Josh Levenberg, „Why Google Stores Billions of Lines of Code in a Single Repository”, Communications of the ACM, iulie 2016. Descrierea canonică a monorepo-plus-trunk-based la scara Google.
  • Nicole Forsgren, Jez Humble și Gene Kim, „Accelerate: The Science of Lean Software and DevOps” (IT Revolution, 2018). Argumentul empiric pentru trunk-based development ca practică de înaltă performanță.
  • Proiectul OpenFeature, https://openfeature.dev/ (consultat 2026-05-01). SDK-ul și specificația de feature flags neutre de furnizor.
  • Blog-ul de inginerie LaunchDarkly și documentația Unleash, ambele linkate de pe site-ul OpenFeature, pentru exemple de nivel producție de sisteme de feature flags și pattern-urile construite în jurul lor.
Caută