Arhitektura sustava: planirajte za potrebe koje se mijenjaju, a ne samo za današnje zahtjeve
Većina sustava ne propada zato što je prva verzija bila prejednostavna. Propadaju zato što je prva verzija neprimjetno postala trajna obveza.
Značajka stiže s rokom, doda se tablica, izloži se krajnja točka, a Docker spremnik uspješno se pokrene. Taj posao može biti sasvim razuman za današnje potrebe. Problem počinje kada izvorne pretpostavke nikada ne postanu vidljive: jedna vrsta korisnika, jedan tijek plaćanja, jedan cilj implementacije, jedna potreba za izvještavanjem. Mjesecima kasnije svaki se novi zahtjev mora provući kroz odabire koji su trebali biti privremeni.
Dobra arhitektura sustava nije pokušaj predviđanja svake budućnosti. To je disciplina koja vjerojatne promjene čini pristupačnima, a današnje rješenje zadržava razumljivim.
Dizajnirajte oko promjene, a ne oko zamišljenog opsega
„Otporno na budućnost” nije koristan cilj. Nijedan dizajn nije otporan na nepoznate zahtjeve, a gradnja za svaku mogućnost stvara apstrakcije koje nitko ne može sigurno mijenjati. Bolje je pitanje: koji će se dijelovi ovog sustava najvjerojatnije promijeniti i koliki bi bio trošak njihove promjene?
Za PHP pozadinski sustav, vjerojatne točke promjene često uključuju poslovna pravila, vanjske integracije, autorizaciju, vlasništvo nad podacima i izvještavanje. Kontroler koji miješa sve te odgovornosti može isprva raditi savršeno, ali svaku kasniju prilagodbu čini rizičnom. Cilj nije podijeliti svaku klasu na desetak slojeva. Cilj je promjenjivim odlukama dati jasno mjesto.
Primjerice, zadržite HTTP odgovornosti u kontrolerima, tijek rada aplikacije u namjenskim servisima ili akcijama, a postojanost podataka iza modela ili repozitorija samo kada repozitorij doista donosi korisnu granicu. Tanak omotač oko ORM upita nije arhitektura. Granica koja sprječava da se detalji pružatelja usluga plaćanja preliju kroz cijelu bazu koda može biti.
Počnite s modularnim monolitom
Za većinu proizvoda modularni monolit najsnažnija je zadana opcija. Zadržava implementaciju, otklanjanje pogrešaka, transakcije i lokalni razvoj jednostavnima, istodobno omogućujući bazi koda da razvije smislene unutarnje granice.
Moduli bi trebali odražavati poslovne sposobnosti, a ne samo tehničke kategorije. Struktura direktorija organizirana isključivo kao Controllers, Services i Models može se pretvoriti u potragu za blagom kako aplikacija raste. Grupiranje koda oko područja kao što su narudžbe, naplata, zalihe i identitet olakšava uočavanje vlasništva i ovisnosti.
Važan test je smjer ovisnosti. Naplata može pitati komponentu za identitet smije li račun platiti, ali identitet ne bi trebao trebati poznavati pravila faktura naplate. Kada moduli slobodno ovise jedni o drugima, svaka značajka postaje izmjena na razini cijelog sustava.
Koristite sučelja tamo gdje je ovisnost stvarna
Sučelja su korisna kada aplikacija ovisi o sposobnosti koja može imati više implementacija ili treba izolaciju od vanjskog sustava. Tijek rada s obavijestima može ovisiti o pošiljatelju, a da ne zna odvija li se isporuka putem e-pošte, reda čekanja ili API-ja treće strane.
interface InvoiceSender
{
public function send(Invoice $invoice): void;
}
final class SendInvoice
{
public function __construct(private InvoiceSender $sender)
{
}
public function handle(Invoice $invoice): void
{
$this->sender->send($invoice);
$invoice->markAsSent();
}
}
Ta je granica vrijedna jer se pravila isporuke mogu promijeniti neovisno o tijeku rada s fakturiranjem. Bila bi manje vrijedna kada bi sučelje postojalo samo da sakrije jednog stabilnog internog pomoćnika. Apstrakcija bi trebala smanjiti spregnutost, a ne samo povećati broj datoteka.
Neka baza podataka provodi istine koje posjeduje
Validacija u aplikaciji poboljšava povratne informacije korisniku, ali nije zamjena za integritet baze podataka. Zahtjevi se mogu ponovno pokušati izvršiti, poslovi se mogu pokretati istodobno, skripte mogu zaobići obrazac, a na kraju se može uvesti i drugi put u kodu. Baza podataka konačni je zajednički autoritet nad pohranjenim podacima.
Koristite strane ključeve kada su odnosi obavezni, jedinstvena ograničenja za vrijednosti koje moraju biti jedinstvene te odgovarajuće indekse za upite koje aplikacija doista izvršava. Ako se broj narudžbe ne smije ponavljati, provedite to pravilo u shemi kao i u PHP-u. Ako podređeni zapis ne može postojati bez nadređenog, taj odnos modelirajte izričito.
Razvoj sheme zaslužuje istu pažnju kao i kôd aplikacije. Sigurna migracija često slijedi obrazac proširenja i sužavanja:
- Dodajte novi nullable stupac, tablicu ili indeks bez narušavanja aplikacije koja radi.
- Implementirajte kôd koji može čitati stari oblik i zapisivati novi oblik.
- Po potrebi popunite postojeće podatke u kontroliranim serijama.
- Nakon provjere podataka prebacite čitanja na novi prikaz.
- Uklonite stari put tek nakon što se više ne koristi.
To je manje dramatično od jedne destruktivne migracije, ali implementacijama daje put za oporavak. Također je važno kada više instanci aplikacije tijekom uvođenja nakratko radi s različitim verzijama.
Neka API-ji budu izričiti o ugovorima i neuspjehu
API je dogovor, a ne samo ruta koja vraća JSON. Klijentima su potrebna stabilna značenja polja, predvidive pogreške validacije, ponašanje autorizacije, pravila paginacije i jasan odgovor kada se rad ne može dovršiti.
Verzioniranje je jedna mogućnost, ali kompatibilnost često počinje jednostavnijim navikama: dodajte polja umjesto da ih preimenujete ili uklanjate, izbjegavajte mijenjanje tipa polja i tretirajte korisne podatke o pogreškama kao dio ugovora. Ako javna krajnja točka prihvaća zahtjev koji stvara resurs, ponovni pokušaji zaslužuju posebnu pozornost. Mrežno vremensko ograničenje može ostaviti klijenta nesigurnim je li poslužitelj dovršio posao.
Za operacije koje ne smiju stvarati duplikate, ključ idempotentnosti može poslužitelju omogućiti povezivanje ponovljenih zahtjeva s izvornim rezultatom. Taj se ključ mora dosljedno pohraniti i obraditi; samo prihvaćanje zaglavlja bez bilježenja njegova ishoda ne čini operaciju idempotentnom.
Vanjski API-ji trebaju jednako obrambenu obradu. Postavite vremenska ograničenja, razlikujte neuspjehe koji se mogu ponovno pokušati od trajnih i izbjegavajte slijepo ponovno pokušavanje neidempotentnih zahtjeva. Poslove koji ne zahtijevaju trenutačan rezultat za korisnika stavite u red čekanja, ali dizajnirajte zadatke tako da podnose dvostruku isporuku. Obrada barem jednom je uobičajena; poslovna radnja mora biti sigurna ako se ponovno izvrši.
Spremnici bi trebali učiniti operacije dosadnima
Docker pomaže kada okruženja čini ponovljivima, a ne kada skriva neizvjesnost. Izgradite jednu sliku aplikacije, osigurajte konfiguraciju putem varijabli okruženja ili upravljanog mehanizma za tajne i držite usluge sa stanjem, poput baza podataka, odvojene od spremnika aplikacije.
Produkcijska slika trebala bi instalirati samo ono što joj je potrebno za rad. U tipičnoj PHP aplikaciji ovisnosti se mogu instalirati tijekom izgradnje, dok se zapisivi putovi tijekom izvođenja namjerno identificiraju i montiraju ili dodjeljuju prema potrebi. Izbjegavajte pretpostavku da je lokalni datotečni sustav spremnika trajan između implementacija.
Provjere stanja trebale bi odražavati korisnu spremnost. Proces koji se pokrenuo nije nužno spreman posluživati promet; možda mu još nedostaje povezivost s bazom podataka ili potrebna konfiguracija. Istodobno, krajnja točka za provjeru stanja ne bi trebala obavljati skup posao pri svakom ispitivanju.
Performanse su povratna sprega
Arhitektura se ne može odvojiti od performansi, ali preuranjena optimizacija i dalje je skupa. Izmjerite spori put prije nego što ga redizajnirate. Pregledajte upite baze podataka, potražite ponovljene upite u petljama, provjerite indekse alatima baze podataka za planiranje upita te pratite latenciju reda čekanja i stope pogrešaka.
Predmemoriranje je najučinkovitije kada su njegova pravila vlasništva i invalidacije jasna. Predmemorirajte izvedenu vrijednost zato što je skupa i sigurno ponovno upotrebljiva, a ne zato što se predmemoriranje čini općim lijekom. Zastarjela cijena, dozvola ili broj zaliha mogu biti gori od sporog odgovora.
Kada se usko grlo potvrdi, odaberite najmanju intervenciju koja ga rješava: bolji upit, indeks, paginaciju, pozadinski posao ili ograničenu predmemoriju. Svako rješenje uvodi vlastitu operativnu odgovornost.
Arhitektura je niz reverzibilnih odluka
Najzdraviji sustavi nisu oni s najviše dijagrama ili najviše usluga. To su oni u kojima razvojni programeri mogu objasniti gdje pripada neko pravilo, kako se podaci sigurno mijenjaju, što se događa kada ovisnost zakaže te kako se implementacija može vratiti ili dovršiti.
Izgradite najmanji koherentan sustav za današnje potrebe, a zatim zaštitite njegove spojeve. Imenujte granice, provedite važne invarijante, mjerite prije optimizacije i ostavite prostor za sljedeću vjerodostojnu promjenu. Tako arhitektura ostaje prednost umjesto da postane najskuplji dio baze koda.