Arhitektura sustava: Pragmatizam ispred dogme
Rasprave o arhitekturi mogu postati neobično religiozne. Jedan tim smatra mikroservise jedinom ozbiljnom opcijom. Drugi inzistira na tome da svaki sustav mora početi kao modularni monolit. Netko predlaže sabirnicu događaja prije nego što je prvi korisnik dovršio radni tijek. Netko drugi odbacuje svaku apstrakciju koja nije bila potrebna jučer.
Dobra arhitektura nije natjecanje u dokazivanju čistoće nekog obrasca. Ona je kontinuirana praksa donošenja korisnih kompromisa uz očuvanje mogućnosti da se kasnije donesu bolji.
Za backend inženjere pragmatizam nije izgovor za nemarne prečace. On znači razumijevanje cijene odluke: operativnog opterećenja, načina otkaza, brzine razvoja, dosljednosti baze podataka, sigurnosti, performansi i vjerojatnosti promjene zahtjeva. Najbolji je dizajn obično najjednostavniji onaj koji rješava stvarna današnja ograničenja, a da sutrašnjicu ne čini nepotrebno skupom.
Počnite s ograničenjima, a ne s preferiranim obrascem
Prije odabira servisa, redova, baza podataka ili topologije implementacije, definirajte što sustav zapravo treba raditi. Ograničenja mogu biti tehnička, ali ih često oblikuju proizvod i tim.
- Koliko korisnika i zahtjeva sustav sada mora podržati i kakav je rast realno moguć?
- Koji radni tijekovi zahtijevaju snažnu dosljednost, a koji mogu tolerirati kašnjenje?
- Koji su podaci osjetljivi, regulirani ili ih je teško oporaviti?
- Koliko će inženjera održavati sustav i upravljati njime kada nešto zakaže?
- Što je skuplje: odgođena isporuka, zastoj, netočni podaci ili sporo vrijeme odziva?
Mala PHP aplikacija s konvencionalnom relacijskom bazom podataka može podržati iznenađujuću količinu poslovne složenosti. Može pružiti autentikaciju, integracije naplate, pozadinske poslove, izvještavanje i dobro dizajniran API bez trenutačne potrebe za zasebnim servisima koji se mogu samostalno implementirati. Prerano razdvajanje često stvara nove probleme: distribuirano praćenje, dupliciranu konfiguraciju implementacije, verzionirane ugovore, mrežne kvarove i složen lokalni razvoj.
To ne znači da je monolit univerzalno ispravan. Komponenta kojoj su potrebni radnici koji se mogu neovisno skalirati, koja ima zasebnu granicu pouzdanosti ili koju održava odvojeni tim, može zaslužiti izolaciju. Bit je u tome da granica treba riješiti poznat problem, a ne samo nalikovati dijagramu arhitekture iz veće tvrtke.
Izgradite modularni monolit prije distribuiranja složenosti
Monolit nije automatski zapetljan, kao što ni mikroservisi nisu automatski modularni. Korisna je razlika u tome ima li kôd razumljive granice.
U PHP backendu organizirajte ponašanje oko domena, a ne samo oko tehničkih slojeva. Modul narudžbi, na primjer, može upravljati stvaranjem narudžbi, pravilima određivanja cijena, operacijama pohrane i događajima povezanima s narudžbama. Drugi moduli trebali bi komunicirati kroz promišljena sučelja, umjesto da izravno pristupaju internim tablicama ili klasama.
final class CreateOrder
{
public function __construct(
private OrderRepository $orders,
private PaymentGateway $payments
) {
}
public function handle(CreateOrderRequest $request): Order
{
$order = Order::fromRequest($request);
$this->payments->authorize($order->total());
$this->orders->save($order);
return $order;
}
}
Točan okvir i raspored direktorija manje su važni od discipline koja stoji iza njih. Neka HTTP kontroleri budu tanki. Izbjegavajte stavljati poslovna pravila u migracije baze podataka, predloške ili povratne pozive okvira. Učinite ovisnosti vidljivima. Pišite testove oko smislenog ponašanja. Ti izbori omogućuju buduće izdvajanje ako modul doista treba postati servis.
Izdvajanje bi trebalo biti posljedica pritiska, a ne spekulativna investicija. Kada je granica već jasna unutar monolita, premještanje iza API-ja ili reda teško je, ali razumljivo. Kada granica ne postoji, izdvajanje servisa obično premješta zbrku umjesto da je ukloni.
Birajte baze podataka zbog ispravnosti prije noviteta
Odluke o bazama podataka još su jedno mjesto gdje moda može nadvladati prosudbu. Relacijska baza podataka često je najpragmatičniji zadani izbor za operativne sustave jer transakcije, ograničenja, spajanja i zreli alati izravno odgovaraju na uobičajene poslovne potrebe.
Ako račun mora imati valjanog kupca, prikazujte taj odnos stranim ključem gdje je to prikladno. Ako saldo ne smije postati negativan, razmislite pripada li ta invarijanta logici aplikacije, transakcijskim ažuriranjima ili ograničenju baze podataka. Čist objektni model ne štiti podatke kada istodobno pristignu istodobni zahtjevi.
Pragmatizam također znači korištenje specijalizirane pohrane kada to radno opterećenje opravdava. Indeks pretraživanja može poboljšati pronalaženje. Spremište objekata može odgovarati prenesenim datotekama. Predmemorija može smanjiti ponovljena skupa čitanja. No svako dodatno spremište podataka uvodi pitanja vlasništva: koja je kopija mjerodavna, kako se podaci sinkroniziraju, kako se kvarovi popravljaju i kako će funkcionirati sigurnosne kopije i kontrole pristupa?
Predmemoriju općenito treba smatrati potrošnom. Ako njezino brisanje ošteti proizvod, nije bila samo predmemorija; bila je nedokumentirano primarno spremište podataka.
Dizajnirajte API-je kao ugovore, a ne kao izlaz kontrolera
API postaje arhitektura u trenutku kada drugi klijent ovisi o njemu. Taj klijent može biti preglednik, mobilna aplikacija, partnerska integracija, alat naredbenog retka ili drugi interni proces. Stabilni ugovori zaslužuju promišljen dizajn.
Koristite eksplicitnu validaciju zahtjeva, predvidljive odgovore na pogreške i jasne provjere autorizacije. Držite pitanja prijenosa odvojenima od ponašanja domene. Ako krajnja točka stvara resurs, odlučite što se događa kada klijent ponovi pokušaj nakon isteka vremena. Dvostruko plaćanje ili dvostruka narudžba nisu rubni slučajevi male važnosti; normalna su posljedica nepouzdanih mreža.
Ključevi idempotentnosti mogu učiniti operacije osjetljive na ponovni pokušaj sigurnijima. Na primjer, klijent može poslati jedinstveni ključ uz zahtjev za plaćanje, a poslužitelj može vratiti izvorni ishod kada ponovno vidi taj ključ. Implementacija mora dovoljno pouzdano pohraniti ključ i ishod da preživi relevantni prozor za ponovni pokušaj. Samo prihvaćanje zaglavlja bez provođenja takvog ponašanja stvara lažan osjećaj sigurnosti.
Verzioniranje također treba biti razmjerno. Verzija u svakom URL-u nije zamjena za disciplinu kompatibilnosti. Kad je moguće, prednost dajte aditivnim promjenama, jasno dokumentirajte ponašanje i uklanjajte polja tek nakon što potrošači dobiju realan put migracije.
Koristite Docker za smanjenje razlika, a ne za njihovo skrivanje
Docker je vrijedan kada razvojna, testna i implementacijska okruženja čini ponovljivijima. Slika spremnika može zapakirati PHP izvođenje, proširenja i ovisnosti aplikacije tako da se softver pokreće s manje iznenađenja na različitim računalima.
Ne uklanja operativne probleme. Konfiguraciju i dalje treba sigurno pružiti. Migracije baze podataka i dalje trebaju koordinaciju. Zapisnici moraju stići na mjesto koje operateri mogu pregledati. Tajne ne bi trebale biti ugrađene u slike. Spremnik koji se uspješno pokrene ipak može zakazati jer ne može dosegnuti svoju bazu podataka, nema dozvolu za pisanje u potreban direktorij ili koristi zastarjelu konfiguraciju.
Neka put implementacije bude dovoljno jednostavan da ga možete razumjeti pod pritiskom. Kad je moguće, neka provjere stanja odražavaju smisleno stanje spremnosti. Odvojite artefakte vremena izgradnje od konfiguracije vremena izvođenja. Testirajte plan migracije i vraćanja na prethodnu verziju prije nego što produkcijski incident nametne problem.
Mjerite prije optimiziranja
Rad na performansama najučinkovitiji je kada počinje vidljivim uskim grlom. Krajnja točka može biti spora zbog neindeksiranog upita, ponovljenog pristupa bazi podataka, prekomjerne serijalizacije, vanjskog API poziva ili radnika reda koji ne prati tempo. Svaki problem zahtijeva drukčije rješenje.
U PHP aplikacijama uobičajena poboljšanja uključuju izbjegavanje N+1 upita, odabir samo potrebnih stupaca, dodavanje indeksa koji odgovaraju stvarnim obrascima upita, premještanje nebitnog rada u pozadinske poslove i pažljivo predmemoriranje odabranih rezultata s velikim brojem čitanja. Ništa od toga ne treba primjenjivati slijepo. Indeks ubrzava neka čitanja, a povećava trošak pisanja. Red poboljšava vrijeme odziva, a uvodi rukovanje asinkronim kvarovima. Predmemoriranje smanjuje opterećenje, a stvara pravila poništavanja.
Praktično pitanje nije: „Koja je najbrža arhitektura?” Ono glasi: „Što je dovoljno sporo da je važno, zašto je sporo i koja ga promjena poboljšava bez stvaranja većeg problema?”
Zadržite mogućnost izbora tamo gdje je promjena vjerojatna
Održivost je uvelike sposobnost sigurne promjene sustava. To ne zahtijeva izgradnju točaka proširenja za svaku zamišljenu budućnost. Zahtijeva prepoznavanje područja koja će se najvjerojatnije mijenjati: pružatelja plaćanja, pravila određivanja cijena, integracija trećih strana, kanala obavijesti i potreba izvještavanja.
Koristite sučelja tamo gdje postoji stvarna alternativa ili vjerodostojna granica. Koristite konfiguraciju za stvarne razlike među okruženjima. Izbjegavajte apstrakcije koje samo omataju jednu metodu i otežavaju praćenje kôda. Cilj nije maksimalna fleksibilnost; cilj je korisna fleksibilnost.
Arhitektura ostvaruje svoju vrijednost kada sljedeću važnu promjenu čini sigurnijom, jasnijom ili jeftinijom.
Pragmatični sustavi nisu pojednostavljeni. Ozbiljno shvaćaju pouzdanost, priznaju neizvjesnost i zadržavaju složenost vezanu uz dokazanu potrebu. Počnite s jasnim granicama, zaštitite integritet podataka, dizajnirajte za uobičajene kvarove i mjerite probleme koje korisnici stvarno osjećaju. Zatim neka se arhitektura razvija kao odgovor na dokaze. To je manje dramatično od dogme, ali tako softver ostaje koristan dugo nakon što njegov prvi dijagram zastari.