Razvoj

System Design: Architecting for Tomorrow's Unknowns

Dizajn sustava: Arhitektura za sutrašnje nepoznanice

Najteži dio dizajna sustava nije odabir baze podataka, crtanje dijagrama ili dodavanje reda za poruke. Radi se o donošenju odluka danas bez pretvaranja da možete vidjeti svaki zahtjev koji dolazi sutra.

Dobra arhitektura ne uklanja promjene. Ona promjenama daje sigurno mjesto na koje mogu doći. Ta je razlika važna jer pozadinski sustavi rijetko propadaju zbog manjka domišljatosti; propadaju kada uobičajena evolucija postane skupa, rizična i teška za razumjeti.

Dizajnirajte za promjenu, a ne za zamišljeni opseg

„Otporno na budućnost” često je skraćenica za izgradnju daleko više mehanizama nego što ih trenutačni proizvod treba. Jednostavna aplikacija dobije sabirnice događaja, više servisa, distribuirano praćenje i nekoliko spremišta podataka prije nego što ima dovoljno prometa ili organizacijske složenosti da bi išta od toga opravdala.

Korisniji je cilj izbornost: zadržati mogućnost uvođenja vjerojatne buduće promjene bez obvezivanja na svaku moguću buduću arhitekturu. Počnite s najjednostavnijim dizajnom koji jasno zadovoljava današnje potrebe, a zatim postavite granice oko područja koja će se najvjerojatnije razvijati.

Za PHP aplikaciju to često znači modularni monolit, umjesto trenutačnog uvođenja mikroservisa. Jedan isporučivi sustav i dalje može imati jasne module za naplatu, identitet, katalog, obavijesti i izvještavanje. Svaki modul posjeduje svoja poslovna pravila i ostatku aplikacije izlaže promišljeno sučelje.

To nije kompromisna arhitektura. To je način da transakcije, implementacije, otklanjanje pogrešaka i lokalni razvoj ostanu jednostavni, uz prostor za kasnije izdvajanje modula ako se njegove potrebe za skaliranjem, vlasništvom ili izdanjima doista raziđu.

Odaberite granice koje odražavaju poslovanje

Tehnički slojevi korisni su, ali nisu dovoljni. Projekt organiziran samo na kontrolere, servise, repozitorije i modele može postati težak za mijenjanje jer se nepovezani poslovni koncepti nađu povezani putem zajedničkih klasa i tablica.

Umjesto toga, počnite pitanjem gdje se poslovna pravila razlikuju. Narudžba nije samo redak u bazi podataka. Ima pravila o cijenama, ispunjenju, otkazivanju, plaćanju i komunikaciji s kupcima. Ta pravila zaslužuju koherentno mjesto.

Praktična granica modula trebala bi razjasniti nekoliko stvari:

  • Koje podatke i invarijante modul posjeduje.
  • Koje operacije drugi moduli mogu zatražiti.
  • Koje događaje ili rezultate objavljuje nakon značajnih promjena.
  • Koji detalji ostaju privatni izbori implementacije.

Primjerice, modul naplate ne bi trebao izravno ažurirati tablice zaliha samo zato što poznaje njihovu shemu. Trebao bi zatražiti od modula zaliha da rezervira zalihu. To se u početku može činiti pomalo formalnijim, ali sprječava da mali prečac postane trajna ovisnost.

Sučelja neka budu uža od implementacija

Potrošačima obično treba manje nego što pružatelji žele izložiti. Krajnjoj točki API-ja možda trebaju samo javni naziv proizvoda, cijena i dostupnost, a ne cijeli model postojanosti s bilješkama dobavljača i poljima internog radnog toka.

Vraćanje namjerno oblikovanih odgovora smanjuje slučajnu povezanost i čini promjene sheme sigurnijima. Isto načelo vrijedi unutar baze koda: ovisite o malom ugovoru umjesto o konkretnoj klasi s desecima metoda.

interface InventoryReservations
{
    public function reserve(string $sku, int $quantity): ReservationResult;
}

Vrijednost nije u samom sučelju. Vrijednost je u tome što naplata ovisi o mogućnosti rezerviranja zaliha, a ne o strategiji baze podataka koju zalihe slučajno koriste danas.

Neka baza podataka provodi ono što je važno

Kôd aplikacije mjesto je gdje živi poslovno ponašanje, ali baza podataka ostaje konačni autoritet za pohranjene podatke. Važne invarijante ne bi trebale ovisiti isključivo o validatoru zahtjeva ili grani kontrolera koju mogu zaobići posao, skripta za uvoz ili buduća krajnja točka.

Koristite odgovarajuća ograničenja: primarne ključeve, strane ključeve tamo gdje je vlasništvo stabilno, jedinstvene indekse, stupce koji ne dopuštaju null i transakcije baze podataka za promjene koje moraju zajedno uspjeti ili ne uspjeti. Ako adresa e-pošte mora biti jedinstvena, učinite je jedinstvenom u bazi podataka. Ako zapis plaćanja mora upućivati na narudžbu, izričito modelirajte taj odnos.

Istodobno, izbjegavajte pretvaranje svakog upita u vježbu apstrakcije. Repozitorij koji samo omotava jednoredni ORM poziv pruža malo zaštite. Uvedite bogatiju granicu pristupa podacima kada upiti, pravila postojanosti, predmemoriranje ili ponašanje transakcija postanu dovoljno značajni da zaslužuju imenovano mjesto.

Gradite API-je kao dugoročne ugovore

API je obećanje dano klijentima koje možda ne kontrolirate. To uključuje mobilne aplikacije, partnerske integracije, zakazane skripte i vlastito sučelje koje se implementira prema drugačijem rasporedu.

Neka to obećanje bude jednostavno i izričito. Validirajte ulaz na granici, vraćajte predvidljive formate pogrešaka, definirajte ponašanje paginacije i izbjegavajte izlaganje odgovora oblikovanih prema bazi podataka kao javnog ugovora. Kada je promjena neizbježna, radije uvodite dodatne promjene: uvedite novo neobavezno polje prije uklanjanja staroga.

Idempotentnost zaslužuje posebnu pozornost kod operacija koje stvaraju prijenos novca, rezervacije ili vanjske nuspojave. Mreže ponovno pokušavaju. Klijenti ponovno pokušavaju. Redovi ponovno isporučuju poruke. Zahtjev koji je siguran jednom mora biti dizajniran tako da bude siguran i kada stigne dvaput.

$key = $request->header('Idempotency-Key');

if ($existing = $payments->findByIdempotencyKey($key)) {
    return $existing->response();
}

return $database->transaction(function () use ($key, $request) {
    return $payments->createOnce($key, $request->validated());
});

Točna implementacija razlikovat će se, ali ključan je detalj trajno stanje zaštićeno jedinstvenim ograničenjem ili ekvivalentnim mehanizmom sigurnim pri konkurentnom pristupu. Provjera u memoriji nije dovoljna kada zahtjevi mogu doći do više instanci aplikacije.

Namjerno koristite asinkroni rad

Redovi su izvrsni za posao koji ne mora završiti prije nego što korisnik primi odgovor: slanje e-pošte, generiranje izvoza, obradu slika i obavještavanje vanjskih sustava. Nisu lijek za nejasno vlasništvo ili nepouzdanu logiku.

Svaki pozadinski posao treba priču o neuspjehu. Definirajte ponašanje ponovnih pokušaja, osigurajte da je ponovljeno izvršavanje sigurno, zabilježite dovoljno konteksta za dijagnostiku i odlučite što se događa nakon što su ponovni pokušaji iscrpljeni. Posao koji tiho nestane gori je od sinkronog neuspjeha jer stvara lažno povjerenje.

Za događaje koji slijede nakon ažuriranja baze podataka razmotrite razmak između potvrđivanja podataka i objavljivanja poruke. Ako se proces zaustavi nakon što jedno uspije, a prije drugoga, sustavi se mogu razići. Obrazac transakcijskog izlaznog spremnika može pomoći: zabilježite odlazni događaj u istoj transakciji, zatim ga objavite zasebno i označite isporučenim tek nakon uspjeha.

Neka operacije budu dio dizajna

Docker može učiniti lokalna okruženja i implementacije ponovljivijima, ali spremnik nije automatski operativni plan. Konfiguracija bi trebala dolaziti iz okruženja, tajne bi trebale ostati izvan slika i repozitorija, a zapisi aplikacije trebali bi biti dovoljno strukturirani da povežu zahtjev, posao i pogrešku.

Provjere stanja trebale bi odgovarati na korisna pitanja. To što je proces živ razlikuje se od toga da je aplikacija spremna posluživati promet. Slično tome, rad na performansama trebao bi početi opažanjem: izmjerite spore krajnje točke, pregledajte upite baze podataka, utvrdite zastoje, a zatim optimizirajte stvarno usko grlo.

Prerano predmemoriranje posebno je opasno jer stvara pravila invalidacije prije nego što postoje dokazi da je predmemoriranje potrebno. Najprije dodajte odgovarajuće indekse, uklonite nepotrebne upite, podijelite velike zbirke na stranice i postavite razumne vremenske limite. Predmemorirajte samo kada to podržava izmjereni obrazac pristupa.

Ostavite trag za sljedeću promjenu

Održivost se ne postiže pisanjem više dokumentacije nego koda. Dolazi od toga da važne odluke budu lako pronaći: jasni nazivi, usmjereni moduli, mali testovi oko poslovnih pravila, povijest migracija i sažeti zapisi o arhitektonskim izborima.

Sustav koji dizajnirate nije spomenik. To je radno okruženje za buduće programere, uključujući vašu verziju koja se više ne sjeća zašto je napravljen određeni kompromis. Projektirajte za tu osobu. Neka granice budu iskrene, načini neuspjeha izričiti i dajte prednost dizajnima koji mogu postati sofisticiraniji tek kada to stvarnost od njih zatraži.

Portret autora bloga

Mihajlo

Ja sam Mihajlo — programer vođen znatiželjom, disciplinom i stalnom željom da stvorim nešto smisleno. Dijelim uvide, tutorijale i besplatne usluge kako bih pomogao drugima da pojednostave svoj rad i rastu u svijetu softvera i umjetne inteligencije koji se neprestano razvija.