Iza commita: Arhitektura za jednostavno refaktoriranje PHP-a
Refaktoriranje rijetko ne uspije zato što programer ne može preimenovati klasu. Ne uspije zato što kodna baza promjene čini skupima: kontroler zna previše o pohrani podataka, shema baze podataka prodire u svaki upit, Docker slika potajno pretpostavlja lokalno stanje, a API odgovor postao je nedokumentirani ugovor.
Zato održivost nije faza čišćenja nakon isporuke. Ona je arhitektonsko svojstvo ugrađeno u putove kojima se softver mijenja. U PHP sustavima najvrjedniji dizajnerski rad često se događa prije prvog velikog refaktoriranja: stvaranjem točaka razdvajanja, ograničavanjem ovisnosti i olakšavanjem provjere ponašanja.
Refaktoriranje počinje pronalaženjem stvarne granice
Dobre granice nisu proizvoljne mape. One razdvajaju razloge za promjenu. Pravilo određivanja cijena ne bi trebalo razumjeti HTTP. Upit baze podataka ne bi trebao odlučivati što API klijent vidi. Kontroler bi trebao koordinirati rad, a ne postati mjesto na kojem se gomila svako poslovno pravilo.
Razmotrite krajnju točku koja stvara narudžbu. Krhka implementacija može u jednoj akciji kontrolera validirati unos, izračunati ukupne iznose, zapisati u nekoliko tablica, slati obavijesti i oblikovati JSON. Radi sve dok ne stigne sljedeći zahtjev: kuponi, drugačiji kanal naplate, asinkrona obavijest ili izmijenjeni oblik odgovora.
Otporniji dizajn svakom aspektu daje usku ulogu:
- Kontroleri prevode HTTP zahtjeve u pozive aplikacije i vraćaju odgovore.
- Aplikacijske usluge koordiniraju slučaj uporabe kao što je slanje narudžbe.
- Domenska logika upravlja izračunima i poslovnim odlukama.
- Repozitoriji ili usluge upita izoliraju detalje pohrane ondje gdje je ta apstrakcija zaista korisna.
- Infrastrukturni adapteri upravljaju bazama podataka, redovima čekanja, pružateljima e-pošte i vanjskim API-jima.
To ne zahtijeva arhitekturu opterećenu ceremonijama. Poanta je spriječiti da promjena u jednom sloju prisili promjene svugdje drugdje. Ako pružatelj plaćanja promijeni svoj payload, prijevod bi trebao biti koncentriran u adapteru pružatelja, a ne raspršen po kontrolerima i modelima.
Oslanjajte se na ponašanje, a ne na pogodnosti okvira
PHP okviri pružaju produktivne zadane postavke, ali pogodnost može postati sprega kada objekti okvira prolaze kroz cijelu aplikaciju. Prosljeđivanje HTTP objekta zahtjeva domenskom kodu, na primjer, poslovno pravilo čini težim za ponovnu uporabu i testiranje. Također potiče skrivene ovisnosti o zaglavljima, sesijama i globalnom stanju.
Prednost dajte malim ulazima koji opisuju slučaj uporabe. Objekt naredbe može biti obična PHP klasa s eksplicitnim poljima. Aplikacijska usluga prima tu naredbu, validira poslovnu operaciju i delegira suradnicima čije su odgovornosti vidljive u konstruktoru.
final class PlaceOrder
{
public function __construct(
private OrderRepository $orders,
private PriceCalculator $prices,
) {
}
public function handle(PlaceOrderCommand $command): Order
{
$total = $this->prices->calculate($command->items);
$order = Order::create(
$command->customerId,
$command->items,
$total,
);
$this->orders->save($order);
return $order;
}
}
Primjer je namjerno običan. Njegova vrijednost nije apstrakcija radi same apstrakcije; nego to što se određivanje cijena može razvijati neovisno o pohrani, a slučaj uporabe može se provjeriti bez stvaranja web zahtjeva ili pokretanja cijelog okvira.
Prema bazi podataka odnosite se kao prema dugotrajnom ugovoru
Refaktoriranje baze podataka mjesto je gdje se iznenade čak i inače pažljivi timovi. Aplikacijski kod često se može brzo postaviti i vratiti na prethodnu verziju. Podaci imaju dulje pamćenje. Preimenovani stupac, promijenjena enum vrijednost ili podijeljena tablica možda će trebati podržavati stare i nove verzije aplikacije tijekom postupnog postavljanja, ponovnih pokušaja radnika i odgođenih pozadinskih poslova.
Sigurnija promjena baze podataka obično slijedi obrazac proširenja i sužavanja:
- Dodajte novu shemu bez uklanjanja stare sheme.
- Postavite kod koji može čitati stari oblik i zapisivati novi oblik ili, gdje je potrebno, namjerno sinkronizirati oba.
- Popunite postojeće zapise u kontroliranim serijama.
- Prebacite čitanja na novi prikaz nakon provjere podataka.
- Uklonite stari put tek nakon što sve podržane verzije aplikacije i radnici više ne ovise o njemu.
Precizna implementacija ovisi o sustavu, ali slijed je važan. Izbjegavajte migracije koje kombiniraju destruktivnu promjenu sheme s aplikacijskim kodom koji pretpostavlja da novi svijet već postoji. Postavljanje prekinuto između tih pretpostavki incident je u produkciji koji samo čeka da se dogodi.
Indeksi zaslužuju istu disciplinu. Dodajte ih zato što ih treba izmjereni obrazac upita, a ne zato što ORM odnos izgleda važan. Zatim provjerite plan upita i učinak na trošak zapisivanja. Indeks može ubrzati jedan put čitanja, dok svaki unos i ažuriranje čini skupljima.
Razvoj API-ja održavajte promišljenim
API je granica proizvoda, čak i kada su njegovi jedini korisnici interne usluge. Klijenti mogu predmemorirati odgovore, ponovno pokušavati zahtjeve, strogo deserializirati polja ili ovisiti o rubnim slučajevima koji nikada nisu bili namijenjeni javnosti. Refaktoriranje unutarnjih dijelova krajnje točke sigurno je samo kada njezino vidljivo ponašanje ostaje kompatibilno.
Mapiranje odgovora učinite eksplicitnim. Ne vraćajte modele pohrane izravno samo zato što serializacija to olakšava. Namjenski resurs ili transformer uspostavlja stabilan javni oblik i timu daje jedno mjesto za uvođenje polja, zastarijevanje starih i upravljanje mogućnošću null vrijednosti.
Za promjene koje ne mogu biti kompatibilne, verzioniranje je manje važno od komunikacije i dizajna migracije. Nova krajnja točka ili verzija trebala bi imati jasnu svrhu, definiran prijelazni put i odluku o uklanjanju povezanu sa stvarnim usvajanjem među klijentima. Održavanje više verzija zauvijek nije kompatibilnost; to je odgođena složenost.
Učinite lokalna i postavljena okruženja dosadno sličnima
Docker može poboljšati sigurnost pri refaktoriranju kada ovisnosti aplikacije čini eksplicitnima. Korisna razvojna postava deklarira PHP izvršno okruženje, proširenja, bazu podataka, predmemoriju i pomoćne usluge umjesto da se oslanja na ono što je slučajno instalirano na prijenosnom računalu.
No kontejneri ne uklanjaju razlike među okruženjima. Konfiguracija bi trebala dolaziti iz vrijednosti specifičnih za okruženje, dok zadane postavke i validacija ostaju u aplikaciji. Kontejner bi trebao jasno zakazati kada nedostaje obavezna postavka, umjesto da se pokrene sa slučajnom zamjenskom vrijednošću.
Također odvojite brige izgradnje od briga izvođenja. Razvojne slike mogu uključivati alate za otklanjanje pogrešaka i montiranja izvornog koda; produkcijske slike trebale bi sadržavati samo ono što aplikacija treba za rad. To olakšava promišljanje o artefaktima postavljanja i smanjuje mogućnost da lokalna pogodnost postane produkcijska ovisnost.
Testovi bi trebali štititi odluke, a ne trivijalnosti implementacije
Testovi pogodni za refaktoriranje potvrđuju značajne ishode. Testovi koji simuliraju svaki poziv metode ili provjeravaju privatne detalje implementacije obično bezopasno restrukturiranje čine bolnim. Ako premještanje izračuna u namjensku klasu pokvari desetke testova unatoč nepromijenjenom ponašanju, skup testova provodi povezivanje umjesto ispravnosti.
Uravnotežena PHP strategija testiranja obično uključuje brze jedinične testove za poslovna pravila, integracijske testove za repozitorije i granice okvira te manji broj testova od kraja do kraja za kritične tijekove rada. Točna kombinacija varira, ali svaki sloj trebao bi odgovoriti na zasebno pitanje.
- Izračunava li pravilo određivanja cijena očekivani ukupni iznos?
- Pohranjuje li repozitorij i dohvaća li zapise ispravno u odnosu na stvarno ponašanje baze podataka?
- Može li klijent dovršiti ključni API tijek rada s valjanom autentifikacijom i pogreškama validacije?
Testovi su posebno vrijedni oko putova neuspjeha: duplicirani zahtjevi, kršenja ograničenja baze podataka, vremenska ograničenja vanjskih usluga i poslovi koji se ponovno pokušavaju. To nisu neuobičajeni slučajevi u distribuiranim sustavima. To su normalni radni uvjeti koje bi arhitektura trebala učiniti razumljivima.
Ostavite kodnu bazu lakšom za promjenu nego što ste je zatekli
Cilj refaktoriranja nije ljepši diff. To je sustav u kojem sljedeća promjena ima manji radijus utjecaja, jasnije vlasništvo i vjerodostojan način dokazivanja da radi. Taj ishod proizlazi iz skromnih, ponovljenih odluka: izolirajte ovisnost, imenujte poslovnu operaciju, zaštitite migraciju, definirajte API ugovor i testirajte ponašanje na odgovarajućoj razini.
Iza commita je mjesto gdje se inženjerska vrijednost umnožava. Kada arhitektura promjenu pretvori iz rizičnog iskapanja u rutinsku operaciju, tim može trošiti manje energije braneći prošlost, a više energije gradeći ono što proizvodu sljedeće treba.