Pragmatična arhitektura: Izgradnja softvera koji nadživljava pompu
Većina softvera ne propada zato što je tim odabrao pogrešan radni okvir. Propada tiše: mali prečac postane konvencija, konvencija postane pretpostavka, a pretpostavku je skupo promijeniti kada stignu stvarni korisnici, stvarni podaci i stvarni rokovi.
Pragmatična arhitektura disciplina je donošenja današnjih odluka bez prevelikog zaduživanja od sutrašnjice. Ona nije argument ni za razrađene apstrakcije ni za minimalizam pristupa „samo isporuči”. To je način da sustav ostane razumljiv, prilagodljiv i pouzdan dok se njegovi zahtjevi još mijenjaju.
Počnite od oblika problema
Arhitektura bi trebala slijediti pritiske s kojima se sustav stvarno suočava. Pozadinski sustav koji prihvaća plaćanja ima drukčije načine otkazivanja od internog alata za izvještavanje. API koji poslužuje mobilnu aplikaciju treba stabilne ugovore. Radni tijek s mnogo podataka možda treba pažljivo indeksiranje mnogo prije nego što mu zatreba više aplikacijskih poslužitelja.
Korisna rana pitanja su konkretna:
- Koji su podaci dovoljno važni da ih treba zaštititi, revidirati ili oporaviti?
- Koji zahtjevi moraju biti brzi, a koji se mogu odvijati asinkrono?
- Što se događa kada je ovisnost spora, nedostupna ili dvaput vrati isti događaj?
- Koji će se dijelovi vjerojatno promijeniti jer tvrtka još uči?
- Kako će razvojni inženjer dijagnosticirati kvar nakon što dosegne produkciju?
Ta pitanja vode do boljih granica od modernog arhitektonskog dijagrama. Skromna PHP aplikacija s jasnim domenskim slojem, relacijskom bazom podataka, radnikom reda i dobrom vidljivošću sustava može biti mnogo otpornija od zbirke servisa čije su vlasništvo i putanje otkaza nejasni.
Odaberite jednostavne granice prije domišljatih apstrakcija
Za mnoge pozadinske sustave dobro strukturirani monolit pragmatično je polazište. „Monolit” ne bi trebao značiti jedan golem sloj kontrolera izravno povezan sa svakom tablicom. On znači jednu aplikaciju koja se može implementirati, s promišljenim unutarnjim granicama: HTTP obrada, aplikacijski radni tijekovi, domenska pravila, pohrana i integracije.
Primjerice, radni tijek narudžbe može izložiti malu aplikacijsku uslugu koja koordinira provjeru valjanosti, rezervaciju zaliha, pohranu i događaj za naknadni rad. Kontroler prevodi HTTP ulaz u taj radni tijek; ne bi trebao postati mjesto na kojem se gomilaju poslovna pravila.
final class PlaceOrder
{
public function __construct(
private OrderRepository $orders,
private Inventory $inventory,
private TransactionManager $transactions,
) {}
public function handle(PlaceOrderRequest $request): Order
{
return $this->transactions->run(function () use ($request): Order {
$this->inventory->reserve($request->items);
$order = Order::fromRequest($request);
$this->orders->save($order);
return $order;
});
}
}
Točni nazivi klasa nisu važni. Važno je da je granica transakcije vidljiva i da se radni tijek može testirati bez HTTP zahtjeva. Vanjske nuspojave, poput dostave e-pošte ili web-dojavnika, obično pripadaju izvan te transakcije. U suprotnom, spor mrežni poziv može držati zaključavanja baze podataka dok obavlja posao koji se kasnije može sigurno ponoviti.
Podijelite servise s razlogom, a ne zbog raspoloženja
Servisi opravdavaju svoj operativni trošak kada trebaju neovisnu implementaciju, neovisno skaliranje, drukčiju sigurnosnu granicu ili uistinu odvojeno vlasništvo. Do tada modularni monolit često omogućuje bržu isporuku i jednostavnije otklanjanje pogrešaka. Poziv metode lakše je pratiti od mrežnog zahtjeva, a o jednoj transakciji baze podataka lakše je rasuđivati nego o distribuiranoj koordinaciji.
Kada granica postane stvarna, izdvojite je uz ugovor, nadzor i plan migracije. Nemojte zamijeniti premještanje koda u drugo spremište sa stvaranjem pouzdanog servisa.
Dizajnirajte API-je kao dugoročna obećanja
API nije samo ruta koja vraća JSON. To je ugovor o kojem klijenti mogu ovisiti dulje nego što se očekuje. Stabilno imenovanje, predvidljivi odgovori na pogreške, paginacija i izričita pravila verzioniranja smanjuju slučajna narušavanja kompatibilnosti.
Neuspjesi provjere valjanosti, autorizacije, nedostajući resursi i neočekivane pogreške poslužitelja trebali bi ostati razlikovni. Klijenti trebaju dovoljno informacija za djelovanje, ali ne bi trebali dobiti tragove stoga, interne SQL poruke ili pojedinosti implementacije.
Za operacije pisanja koje se mogu ponoviti, osobito od klijenata ili web-dojavnika, idempotentnost je važna. Povratni poziv za plaćanje isporučen dvaput ne smije stvoriti dvije plaćene narudžbe. Praktičan obrazac jest pohraniti jedinstveni identifikator vanjskog događaja i duplikat pri umetanju tretirati kao već obrađen događaj, umjesto pokušaja zaključivanja o duplikatima na temelju vremenskih oznaka ili sličnosti sadržaja.
Promjena kompatibilna sa starijim verzijama obično je jeftinija od prisilne sinkronizacije. Dodavanje neobaveznog polja odgovora često je sigurno; promjena značenja ili vrste polja nije. Zastarijevanje je proces proizvoda i komunikacije jednako koliko i tehnički proces.
Neka baza podataka preuzme svoj dio odgovornosti za ispravnost
Aplikacijski kod vrijedan je, ali baze podataka izvrsne su u nametanju činjenica koje moraju ostati istinite uz konkurentnost. Upotrebljavajte strane ključeve kada su odnosi stvarni, jedinstvena ograničenja kada je dupliciranje nevaljano i transakcije kada više promjena mora zajedno uspjeti ili ne uspjeti.
Klasična je pogreška provjeriti dostupnost u aplikacijskom kodu, a zatim kasnije umetnuti podatke bez ograničenja ili prikladne strategije zaključavanja. Pri istodobnim zahtjevima oba pozivatelja mogu vidjeti isto dostupno stanje. Baza podataka mora sudjelovati u zaštiti invarijante.
Rad na performansama slijedi isto načelo: izmjerite stvarnu putanju upita. Indeksi bi trebali podržavati poznate obrasce pristupa, a ne biti dodani iz praznovjerja. Pregledajte spore upite, provjerite planove izvršavanja i osigurajte da paginacija ima izričito poredanje. Paginacija s pomakom može postati skupa na velikim skupovima podataka koji se često mijenjaju; paginacija temeljena na pokazivaču često bolje odgovara kada klijenti prolaze stabilnim redoslijedom sortiranja.
Učinite isporuku ponovljivom
Docker je koristan kada lokalni razvoj i implementaciju čini dosljednijima, a ne kada aplikaciju skriva iza slojeva slučajne složenosti. Spremnik bi trebao jasno deklarirati što mu je potrebno: ovisnosti tijekom izvođenja, konfiguraciju zadanu putem okruženja, predvidljivu naredbu za pokretanje i lokaciju za zapisivanje samo gdje je to nužno.
Spremnost za produkciju također uključuje disciplinu migracija. Pokrećite migracije sheme kao izričit korak implementacije, razumijte je li svaka migracija sigurna uz postojeći promet i izbjegavajte povezivanje izdanja koda s promjenom sheme koja ne može koegzistirati s prethodnom verzijom. Promjene proširi-pa-sažmi često su sigurnije: dodajte novi stupac ili tablicu, implementirajte kod koji podržava oba oblika, migrirajte podatke, a zatim kasnije uklonite stari put.
Ponovna pokušavanja zaslužuju jednaku pažnju. Ponovni pokušaj pri prolaznom mrežnom kvaru može pomoći; ponovno pokušavanje svake iznimke može pojačati prekid rada ili duplicirati nuspojave. Upotrebljavajte ograničeni broj pokušaja, postupno povećavanje razmaka, vremenska ograničenja i idempotentne rukovatelje. Trajno neuspješan pozadinski rad pošaljite na vidljivo mjesto kako bi se mogao istražiti, umjesto da bude tiho odbačen.
Optimizirajte za sljedeću osobu
Održivost nije nejasna sklonost urednom kodu. To je brzina kojom netko može sigurno odgovoriti: na što ova promjena utječe, kako je mogu testirati i što će se dogoditi ako ne uspije?
To znači držati konfiguraciju odvojeno od koda, bilježiti smislen kontekst bez otkrivanja tajni, izlagati provjere stanja koje odražavaju korisne ovisnosti i pisati testove oko važnog ponašanja, a ne privatnih pojedinosti implementacije. To također znači brisati apstrakcije koje više ništa ne pojašnjavaju.
Najbolja arhitektura rijetko je najimpresivniji dijagram. To je ona koja timu omogućuje da s pouzdanjem napravi promjenu, promatra njezine posljedice, oporavi se kada se stvarnost ne slaže i nastavi učiti bez ponovne izgradnje cijelog sustava. Pompa brzo blijedi. Jasne granice, iskreni kompromisi i sustavi kojima je lako upravljati imaju mnogo dulji vijek trajanja.