Prejšnji teden sem napisal , vendar sem obljubil, da bom razložil, kako jo lahko delno uporabimo koristno. Poskusil bom analizirati, kako se ta predloga običajno uporablja v projektih. Minimalno potreben niz metod za repozitorij:
<?php
interface PostRepository
{
public function getById($id): Post;
public function save(Post $post);
public function delete($id);
}Vendar pa, v resničnih projektih, če so se odločili uporabiti repozitorije, se v njih pogosto dodajo metode za poizvedbe zapisov:
<?php
interface PostRepository
{
public function getById($id): Post;
public function save(Post $post);
public function delete($id);
public function getLastPosts();
public function getTopPosts();
public function getUserPosts($userId);
}Te metode bi lahko bile реализirane preko Eloquent scopes, vendar obremeniti razrede entitet s odgovornostjo za povsem lastne poizvedbe - ni najboljša ideja, in prenos te odgovornosti v razrede repozitorijev se zdi logičen. Ali je to res? Posebej sem vizualno ločil to vmesno strukturo na dva dela. Prvi del metod bo uporabljen v operacijah pisanja.
Standardna operacija pisanja je:
- konstrukcija novega objekta in klic PostRepository::save
- PostRepository::getById, manipulacije z entiteto in klic PostRepository::save
- klic PostRepository::delete
V operacijah pisanja ni uporabe metod za poizvedbe. V operacijah branja pa se uporabljajo samo metode get*. Če preberete o Principu ločevanja vmesnikov (črka I u SOLID), bo jasno, da je naš vmesnik postal prevelik in izvaja vsaj dve različni odgovornosti. Čas je, da ga razdelimo na dva. Metoda getById je potrebna v obeh, vendar se bodo ob zapletanju aplikacije njene implementacije razlikovale. To bomo videli kmalu potem. O neuporabnosti pisanja dela sem pisal v prejšnjem članku, zato o tem v tem preprosto pozabim.
Brezdelni del se mi zdi ne tako neuporaben, saj lahko tudi za Eloquent tukaj obstaja več implementacij. Kako poimenovati razred? Lahko ReadPostRepository, vendar ima v zvezi s predlogo Repository to že malo zveze. Preprosto lahko PostQueries:
<?php
interface PostQueries
{
public function getById($id): Post;
public function getLastPosts();
public function getTopPosts();
public function getUserPosts($userId);
}Njegova implementacija s pomočjo Eloquent je precej preprosta:
limit(/*some limit*/)
->get();
}
/**
* @return Post[] | Collection
*/
public function getTopPosts()
{
return Post::orderBy('rating', 'desc')
->limit(/*some limit*/)
->get();
}
/**
* @param int $userId
* @return Post[] | Collection
*/
public function getUserPosts($userId)
{
return Post::whereUserId($userId)
->orderBy('created_at', 'desc')
->get();
}
}Interfejs trebuie să fie legat de implementare, de exemplu în AppServiceProvider:
app->bind(PostQueries::class,
EloquentPostQueries::class);
}
}Această clasă este deja utilă. Ea își îndeplinește responsabilitatea, degrevând astfel fie controlerele, fie clasa entitate. În controler poate fi utilizată astfel:
$postQueries->getLastPosts(),
]);
}
} Metod PostsController::lastPosts doar cere o implementare de la ea PostsQueries și lucrează cu ea. În provider am legat PostQueries de clasa EloquentPostQueries și în controler va fi inserată această clasă.
Să ne imaginăm că aplicația noastră a devenit foarte populară. Mii de utilizatori pe minut deschid pagina cu ultimele publicații. Cele mai populare publicații sunt, de asemenea, citite foarte des. Bazele de date nu fac față bine unei astfel de încărcări, de aceea se folosește soluția standard - cache. Pe lângă baza de date, o anumită copie a datelor este păstrată într-un depozit optimizat pentru anumite operațiuni - memcached ili redis.
Logica de caching de obicei nu este atât de complexă, dar implementarea ei în EloquentPostQueries nu este foarte corectă (cel puțin din cauza Principiului Responsabilității Unice). Mult mai natural ar fi să folosim un model Decorator și să implementăm caching-ul ca decorare a acțiunii principale:
base = $base;
$this->cache = $cache;
}
/**
* @return Post[] | Collection
*/
public function getLastPosts()
{
return $this->cache->remember('last_posts',
self::LASTS_DURATION,
function(){
return $this->base->getLastPosts();
});
}
// alte metode sunt practic la fel
}Ne pozabite na vmesnik Repository v konstruktorju. Iz nejasnega razloga so tako poimenovali vmesnik za predpomnjenje v Laravelu.
Razred CachedPostQueries izvaja samo predpomnjenje. $this->cache->remember preveri, ali ta vnos obstaja v predpomnilniku, in če ne, pokliče callback ter shrani vrnjeno vrednost v predpomnilnik. Le še vgraditi je treba ta razred v aplikacijo. Potrebujemo, da vsi razredi, ki v aplikaciji zahtevajo realizacijo vmesnika PostQueries dobijo instanco razreda CachedPostQueries. Vendar pa mora sam CachedPostQueries kot parameter v konstruktor prejeti razred EloquentPostQueries, saj ne more delovati brez "prave" realizacije. Spremenimo AppServiceProvider:
app->bind(PostQueries::class,
CachedPostQueries::class);
$this->app->when(CachedPostQueries::class)
->needs(PostQueries::class)
->give(EloquentPostQueries::class);
}
}Vse moje želje se precej naravno opisujejo v provajderju. Tako smo za naše poizvedbe izvajali predpomnjenje, le da smo napisali en razred in spremenili konfiguracijo vsebnika. Preostali koda aplikacije se ni spremenila.
Seveda, za popolno implementacijo predpomnjenja je potrebno še izvesti invalidacijo, da neodstranjena objava ne ostane na spletni strani še nekaj časa, ampak se takoj odstrani. Ampak to so že malenkosti.
Končno: uporabili smo ne enega, ampak kar dva vzorca. Vzorec Command Query Responsibility Segregation (CQRS) predlaga popolno ločevanje operacij branja in pisanja na ravni vmesnikov. Do njega sem prišel preko Principu ločevanja vmesnikov, kar pomeni, da veščo manipuliram z vzorci in načeli ter izpeljem enega iz drugega kot teoremo 🙂 Seveda, ne vsakemu projektu je potrebna tako abstraktna izbira entitet, vendar bom delil s tabo trik. Na začetni stopnji razvoja aplikacije lahko preprosto ustvarite razred PostQueries z običajno implementacijo preko Eloquent:
<?php
final class PostQueries
{
public function getById($id): Post
{
return Post::findOrFail($id);
}
// druge metode
}Ko se bo pojavila potreba po predpomnjenju, lahko na hitro ustvarite vmesnik (ali abstrakten razred) na mestu tega razreda PostQueries, njegovo implementacijo pa kopirate v razred EloquentPostQueries in preidete na shemo, ki sem jo opisal prej. Preostala koda aplikacije se ne mora spremeniti.
Vse te trike z razredi, vmesniki, Dependency Injection in CQRS podrobno opisujem v . Tukaj je tudi razlaga uganke, zakaj so vsi moji razredi v primerih k temu članku označeni kot final.
Izvor: habr.com
