Skriveni trošak arhitekture: kada održivost postane teret
Najskuplja arhitektura nije uvijek ona koja zakaže pod opterećenjem. Često je to ona koja radi savršeno dobro, ali zbog koje svaka uobičajena promjena djeluje kao pregovaranje s prošlošću.
Backend timovi uče se cijeniti razdvajanje odgovornosti, proširivost, jasne granice i dizajn otporan na buduće promjene. To su vrijedni ciljevi. Problem počinje kada arhitektura prestane smanjivati složenost i počne je raspoređivati u slojeve, apstrakcije, konvencije i servise koje je sve potrebno razumjeti prije nego što razvojni inženjer može sigurno promijeniti jedno polje u API odgovoru.
Održivost je ključna. Ali održivost se ne mjeri brojem obrazaca koje sustav koristi. Mjeri se time koliko pouzdano sposoban razvojni inženjer može razumjeti, promijeniti, testirati i upravljati sustavom.
Apstrakcija ima operativni trošak
Svaka apstrakcija obećava pojednostaviti nešto. Repozitorij skriva detalje perzistencije. Servisni sloj centralizira poslovna pravila. Red poruka razdvaja radna opterećenja. Lokalno okruženje temeljeno na Dockeru čini ovisnosti ponovljivima. Svako od toga može biti pravi izbor.
Svako također dodaje trajni trošak: više putanja koda, više konfiguracije, više načina otkazivanja i više koncepata koje novi suradnici moraju naučiti. Trošak nije teorijski. Pojavljuje se tijekom otklanjanja pogrešaka, odgovora na incidente, uvođenja novih članova, promjena sheme i rutinskog rada na značajkama.
Razmotrite jednostavan PHP endpoint koji ažurira korisnikovu postavku obavijesti. U maloj aplikaciji zahtjev može proći od kontrolera do pravila validacije, domenskog servisa i ORM modela. To može biti jasno i prikladno.
U preizgrađenoj verziji isto ažuriranje moglo bi proći kroz naredbeni objekt, obrađivač naredbi, aplikacijski servis, domenski agregat, sučelje repozitorija, implementaciju repozitorija, dispečer događaja, asinkroni slušatelj, projekciju modela za čitanje i pretplatnik za invalidaciju predmemorije. Izvorna operacija nije postala smislenija. Samo je dobila više mjesta na kojima može biti pogrešna.
Pitanje nije može li se svaki sloj zasebno opravdati. Većina može. Pitanje je čini li kombinirana struktura sljedeću promjenu lakšom nego što bi je učinio jednostavniji dizajn.
Održivost je lokalna, a ne ceremonijalna
Timovi ponekad brkaju arhitektonsku ceremonijalnost s održivošću. Kodna baza može imati stroge konvencije imenovanja, sučelja oko svake ovisnosti i pažljivo slojevitu strukturu direktorija, a ipak ostati teška za izmjenu.
Održiv sustav čini važne odnose vidljivima. Kada se API polje promijeni, razvojni inženjeri trebali bi moći pronaći gdje se ono validira, trajno pohranjuje, serijalizira i testira bez praćenja labirinta generičkih pomoćnih funkcija.
To ne znači stavljati sve u jedan kontroler ili napustiti granice. Znači birati granice koje odgovaraju stvarnim obrascima promjena. Ako aplikacija ima stvarne poslovne tijekove rada, servisni ili domenski sloj može ih razjasniti. Ako ima više implementacija pohrane, apstrakcija repozitorija može biti vrijedna. Ako ne postoji nijedan od tih uvjeta, dodavanje takvih struktura „radi dosljednosti” može kod učiniti manje iskrenim.
Prednost dajte nazivima koji otkrivaju posao
Generičke apstrakcije osobito su skupe jer skrivaju namjeru. Klasa nazvana Processor ili Manager čitatelju govori gotovo ništa. Klasa nazvana SubscriptionCancellationService odmah stvara mentalni model svoje odgovornosti.
Isto načelo vrijedi za pristup bazi podataka. Usko usmjerena metoda upita poput findOverdueInvoicesForAccount() često prenosi više od širokog API-ja repozitorija sastavljenog od ponovno upotrebljivih filtara. Ponovna upotreba korisna je kada je ponašanje doista zajedničko. Štetna je kada prisiljava čitatelje da rekonstruiraju poslovni upit iz nekoliko generičkih sastavnih dijelova.
Distribuirani sustavi umnožavaju teret održavanja
Razbijanje monolita na servise može riješiti stvarne probleme: neovisno skaliranje, zasebno vlasništvo nad implementacijom, izolirane sigurnosne granice ili različite zahtjeve pouzdanosti. Također može pretvoriti obične pozive metoda u mrežne ugovore.
Kada zahtjev prijeđe granice procesa, tim mora upravljati vremenskim ograničenjima, ponovnim pokušajima, idempotentnošću, autentikacijom, verzioniranjem, opservabilnošću, djelomičnim neuspjehom i dosljednošću podataka. To nisu detalji implementacije koje treba dodati kasnije. To je arhitektura.
Ponovni pokušaj to dobro ilustrira. Ponovni pokušaj neuspješnog HTTP zahtjeva može biti ispravan za sigurno čitanje. Ponovni pokušaj zahtjeva za kreiranje plaćanja bez strategije idempotentnosti može stvoriti dvostruke naplate. Ponovni pokušaj potrošača poruka bez transakcijskog rukovanja može dvaput zapisati isti zapis u bazi podataka. Red nije jednostavno brži poziv funkcije s odgođenim izvršavanjem.
Prije izdvajanja servisa, zapitajte se odražava li granica implementacije trajnu organizacijsku ili operativnu potrebu. Ako je primarni razlog čišći kod, modularni monolit može pružiti većinu jasnoće bez mrežne složenosti.
- Zadržite module neovisnima unutar jedne aplikacije koja se implementira kada njihovi podaci i ritam izdanja ostaju čvrsto povezani.
- Izdvojite servis kada mu trebaju neovisno vlasništvo, skaliranje, sigurnosne kontrole ili karakteristike dostupnosti.
- Definirajte ponašanje pri neuspjehu prije integracije: ograničenja vremena čekanja, pravila ponovnih pokušaja, očekivanja idempotentnosti i ishode vidljive korisniku.
- Izložite smislene metrike i zapisnike na svakoj udaljenoj granici, jer se otklanjanje pogrešaka u produkciji ne može oslanjati na lokalne pretpostavke.
Dizajn baze podataka trebao bi služiti sustavu koji imate
Odluke o bazi podataka još su jedan čest izvor arhitektonskog pretjerivanja. Visoko normalizirana shema može očuvati integritet i smanjiti dupliciranje, ali također može učiniti uobičajena čitanja skupima i teškima za razumijevanje. Denormalizirani dokument može pojednostaviti putanju čitanja, ali prebacuje posao dosljednosti u aplikacijski kod.
Nijedan pristup nije automatski održiviji. Korisno pitanje je: koje operacije moraju ostati jednostavne, pouzdane i brze?
Na primjer, ako administrativna nadzorna ploča neprestano treba račun, njegov aktivni plan, njegov najnoviji račun i nekoliko agregatnih brojanja, prisiljavanje svakog zahtjeva kroz dugi lanac ORM relacija može prikriti neučinkovit plan upita. Eksplicitan upit za čitanje, namjerni indeks ili mali model za čitanje mogu biti lakši za održavanje jer čine zahtjev za performansama vidljivim.
ORM-ovi su produktivni, ali ne uklanjaju ponašanje baze podataka. Razvojni inženjeri i dalje moraju razumjeti transakcije, zaključavanje, broj upita, indekse i sigurnost migracija. Migracija koja doda stupac koji ne dopušta null vrijednosti, popuni veliku tablicu starim podacima i odjednom implementira kod koji očekuje novo polje može stvoriti operativni rizik čak i kada je PHP kod elegantan.
Dizajnirajte promjene za sigurno uvođenje
Promjene sheme često imaju koristi od postupne implementacije. Najprije dodajte kompatibilnu promjenu sheme. Zatim implementirajte kod koji može raditi i sa starim i s novim prikazima. Popunite podatke gdje je potrebno. Na kraju uklonite zastarjele putanje nakon što prijelaz bude dovršen.
Ovaj slijed može djelovati manje uredno od jedne migracije, ali poštuje stvarnost da se aplikacijski kod, stanje baze podataka, pozadinski radnici i implementirani spremnici ne mijenjaju uvijek u potpuno istom trenutku.
Docker bi trebao smanjiti trenje, a ne simulirati produkcijski labirint
Spremnici su vrijedni kada lokalno postavljanje čine ponovljivim, a ovisnosti eksplicitnima. Postaju teret kada razvojnom inženjeru treba desetak servisa, nekoliko datoteka s tajnama i dugačak vodič za otklanjanje poteškoća samo da bi pokrenuo malu granu značajke.
Korisno lokalno okruženje daje prednost brzom putu. Razvojni inženjeri trebali bi moći pokrenuti aplikaciju, izvršiti testove, pregledati zapisnike i poništiti jednokratne podatke razumljivim naredbama. Paritet s produkcijom važan je, ali savršena imitacija rijetko je praktična. Lokalno okruženje trebalo bi reproducirati rizike koji su važni, a ne svaki detalj infrastrukture.
Isto vrijedi za CI konfiguraciju, zastavice značajki, slojeve predmemoriranja i cjevovode implementacije. Složenost koja štiti značajno ograničenje ulaganje je. Složenost koja postoji samo zato što „ozbiljni sustavi to rade ovako” obično je dug s izvrsnom dokumentacijom.
Pragmatični test arhitekture
Prije uvođenja novog sloja, servisa, značajke okvira ili ovisnosti o platformi, postavite nekoliko izravnih pitanja:
- Koji konkretan problem ovo rješava danas?
- Koja će promjena postati lakša nakon što to postoji?
- Koje nove načine otkazivanja i operativne zadatke uvodi?
- Može li tim to objasniti i testirati bez oslanjanja na jednog stručnjaka?
- Bi li jednostavniji dizajn ispunio trenutačni zahtjev s jasnim putem nadogradnje?
Ne postoji nagrada za najsloženiji dijagram arhitekture. Dobro inženjerstvo ostavlja prostor za rast bez prisiljavanja sadašnjeg sustava da nosi punu težinu hipotetskih budućnosti.
Najbolji održivi sustavi nisu oni s najmanje odluka. To su oni čije odluke ostaju čitljive. Kada arhitektura svakodnevni rad čini mirnijim, sigurnijim i bržim, zaslužuje svoj trošak. Kada zbog nje jednostavne promjene djeluju opasno, vrijeme je da uklonite sloj, spojite servis, pojednostavite ugovor ili s pouzdanjem odaberete dosadno rješenje.