Razvoj

Decoupling Dependencies: Building Loosely Coupled PHP Systems

Razdvajanje ovisnosti: Izgradnja slabo povezanih PHP sustava

Većina PHP aplikacija ne postaje teška zbog jedne loše klase. Postaju teške zato što svaka klasa postupno nauči previše o ostatku sustava: upravljačkom programu baze podataka, pružatelju usluge e-pošte, objektu zahtjeva okvira, SDK-u za plaćanja i točnom načinu pohrane konfiguracije.

Takva se praktičnost isprva čini produktivnom. Kasnije mala promjena postaje niz izmjena u kontrolerima, servisima, naredbama i testovima. Razdvajanje ovisnosti praksa je zaustavljanja tog širenja. Svakom dijelu aplikacije daje jasan zadatak i manji skup pretpostavki o svijetu koji ga okružuje.

Povezanost nije neprijatelj; nekontrolirana povezanost jest

Sustav ne može imati nula ovisnosti. Servis naplate na kraju mora teretiti pružatelja usluge plaćanja, a repozitorij na kraju mora komunicirati s bazom podataka. Cilj nije pretvarati se da ti odnosi ne postoje. Cilj je smjestiti ih na namjerne granice.

Slaba povezanost znači da poslovno pravilo ovisi o mogućnosti, a ne o određenoj implementaciji. Primjerice, tijeku obrade narudžbe možda treba nešto što može naplatiti plaćanje. Ne bi trebao znati pruža li tu mogućnost Stripe, bankovni pristupnik ili testni duplikat.

Praktična korist nije arhitektonska čistoća. To je ograničavanje promjena. Kada se infrastruktura promijeni, potrebno je izmijeniti manje datoteka, a ponašanje koje je važno može se testirati bez sastavljanja cijele aplikacije.

Započnite sa smjerom ovisnosti

Poslovna logika trebala bi biti blizu središta dizajna. Kôd okvira, prilagodnici baze podataka, HTTP klijenti, redovi i SDK-ovi dobavljača pripadaju rubovima. Ovisnosti bi općenito trebale pokazivati prema unutra: rub se prilagođava potrebama poslovne logike, umjesto da se poslovna logika prilagođava svakom vanjskom detalju.

Čest znak upozorenja jest klasa orijentirana na domenu koja sama stvara svoje suradnike:

final class InvoiceService
{
    public function send(int $invoiceId): void
    {
        $invoice = new PDOInvoiceRepository();
        $mailer = new VendorMailer();

        // Load, render, and send the invoice.
    }
}

Ova je klasa sada odgovorna za ponašanje računa i za odabir konkretne infrastrukture. Ne može se izvršavati bez integracije baze podataka i e-pošte, a promjena bilo kojeg pružatelja znači uređivanje servisa.

Umjesto toga, izrazite ovisnosti u konstruktoru i ovisite o malim sučeljima u vlasništvu aplikacije:

interface InvoiceRepository
{
    public function getById(int $id): Invoice;
}

interface InvoiceSender
{
    public function send(Invoice $invoice): void;
}

final class InvoiceService
{
    public function __construct(
        private InvoiceRepository $invoices,
        private InvoiceSender $sender,
    ) {
    }

    public function send(int $invoiceId): void
    {
        $this->sender->send($this->invoices->getById($invoiceId));
    }
}

Repozitorij temeljen na bazi podataka i prilagodnik e-pošte specifičan za pružatelja mogu implementirati ta sučelja na drugom mjestu. Servis zadržava fokus: koordinira poslovnu operaciju.

Koristite ubacivanje ovisnosti kao dizajnersku naviku

Ubacivanje ovisnosti često se uvodi kao značajka spremnika, ali spremnik je sporedan. Važna je navika učiniti ovisnosti vidljivima. Ubrizgavanje kroz konstruktor čini potrebne suradnike eksplicitnima, stvara valjane objekte od početka i čini testove jednostavnima.

U aplikaciji okvira spremnik može sastaviti graf objekata na rubu sustava. To je korisno, ali izbjegavajte dopustiti da dohvaćanja iz spremnika prodru u poslovni kôd. Poziv poput container->get() skriva stvarne ovisnosti klase i pretvara spremnik u lokator servisa.

Za jednostavne aplikacije sastavljanje može biti jednako jasno i bez spremnika:

$repository = new PdoInvoiceRepository($pdo);
$sender = new MailProviderInvoiceSender($mailClient);
$service = new InvoiceService($repository, $sender);

Bilo da se to povezivanje nalazi u pružatelju okvira, datoteci za pokretanje ili ulaznoj točki naredbe, držite ga blizu pokretanja aplikacije. Ondje pripadaju konkretni odabiri.

Dizajnirajte sučelja oko svog slučaja uporabe

Sučelja su vrijedna kada štite značajnu granicu, a ne kada svaka klasa po zadanom dobije jedno. Sučelje nazvano DatabaseInterface s desecima generičkih metoda obično izlaže previše infrastrukture. Usko sučelje poput InvoiceRepository opisuje ono što aplikacija stvarno treba.

Ta razlika također sprječava da apstrakcija postane ceremonija. Stvorite sučelje kada vrijedi barem jedno od sljedećeg:

  • Ovisnost je vanjska infrastruktura, poput pohrane, razmjene poruka, vremena ili HTTP API-ja.
  • Poslovni sloj trebao bi ostati neovisan o okviru ili paketu dobavljača.
  • Trebate kontroliranu testnu zamjenu za ponašanje koje je sporo, nedeterminističko ili skupo.
  • Više od jedne implementacije vjerojatno je sada ili će vjerojatno biti kako se sustav razvija.

Nemojte uvoditi sučelje samo zato što klasa postoji. Mali vrijednosni objekt ili usmjereni interni servis mogu ostati konkretni dok se ne pojavi stvarna granica.

Zadržite brige okvira i prijenosa na rubu

Kontroleri bi trebali prevoditi HTTP brige u ulaz aplikacije i prevoditi rezultate natrag u HTTP odgovore. Ne bi trebali sadržavati tijek transakcije, izgradnju upita ili rukovanje pogreškama specifično za pružatelja.

Isto pravilo vrijedi za rukovatelje naredbenog retka, potrošače redova i zakazane poslove. Svaki je prilagodnik oko istog ponašanja aplikacije. Kada je osnovni tijek rada neovisan o objektu zahtjeva ili objektu naredbe, može se ponovno upotrijebiti bez kopiranja logike.

Transakcije baze podataka koristan su primjer granice koja treba pažljiv smještaj. Servis slučaja uporabe može definirati transakcijsku jedinicu rada, dok se izvršitelj transakcije isporučuje kao ovisnost. Time se čuva poslovno značenje atomarnosti bez poučavanja svakog slučaja uporabe o PDO metodama ili fasadi okvira.

Ne propuštajte modele dobavljača kroz aplikaciju

Prosljeđivanje ORM entiteta, objekata odgovora SDK-a ili neobrađenih asocijativnih polja posvuda čini da ostatak baze koda naslijedi podatkovni model dobavljača. Preslikajte vanjske podatke u objekte specifične za aplikaciju na granici. Preslikavanje se može činiti ponavljajućim, ali lokalizira trošak budućih promjena i olakšava uočavanje invarijanti.

To ne znači da svaka tablica zahtijeva razrađen domenski model. Pragmatizam je važan. Upit za izvještavanje samo za čitanje može opravdano vratiti jednostavnu podatkovnu strukturu. Ključno je pitanje prodire li prikaz u odluke koje bi trebale nadživjeti trenutačni odabir perzistencije ili API-ja.

Testirajte ponašanje bez ponovne izgradnje svijeta

Slaba povezanost poboljšava testove jer se važni testovi mogu izvršavati uz male, determinističke suradnike. Lažni repozitorij ili pošiljatelj trebao bi implementirati isti ugovor, bilježiti korisne interakcije i ponašati se dovoljno realno za scenarij. Ne bi trebao samo reproducirati unutarnje pojedinosti produkcijske implementacije.

Ipak, jedinični testovi nisu dovoljni. Prilagodnicima su potrebni integracijski testovi u odnosu na stvarnu bazu podataka, HTTP protokol ili sandbox pružatelja, gdje je dostupan. Zdrava je podjela jednostavna: testirajte poslovna pravila kroz uske ugovore, zatim testirajte svaki infrastrukturni prilagodnik ondje gdje se susreće sa stvarnom tehnologijom.

Razdvajajte postupno, ne jednom preradom

Naslijeđeni PHP kôd rijetko postaje slabo povezan dramatičnim redizajnom. Počnite na bolnoj točki spajanja: servisom koji izravno stvara HTTP klijent, kontrolerom koji sadrži poslovna pravila ili repozitorijem koji vraća objekte specifične za okvir. Uvedite jedan mali ugovor, premjestite konkretnu implementaciju iza njega i povežite je na granici aplikacije.

Nakon svake promjene zapitajte se čini li nova ovisnost klasu lakšom za razumijevanje i testiranje. Ako samo dodaje slojeve bez pojašnjavanja odgovornosti, pojednostavite je.

Trajna korist

Slabo povezani PHP sustavi nisu sustavi s najviše sučelja. To su sustavi u kojima promjene imaju očito mjesto. Pružatelj plaćanja mijenja se u svom prilagodniku. Upit baze podataka mijenja se u svom repozitoriju. Poslovno pravilo mijenja se u slučaju uporabe koji ga posjeduje.

Ta jasnoća održava bazu koda prilagodljivom dugo nakon prvog izdanja. Gradite granice ondje gdje je promjena vjerojatna, zadržite konkretnu tehnologiju na rubovima i dopustite središtu aplikacije da govori jezikom poslovanja. Rezultat nije samo lakše testiranje. To je softver koji ostaje razumljiv kada je najvažnije: tijekom promjene.

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.