Prošli tjedan sam napisao , no obećao sam objasniti kako se može djelomično iskoristiti s koristima. Pokušat ću analizirati kako se obično koristi ovaj predložak u projektima. Minimalno potrebni skup metoda za repozitorij:
<?php
interface PostRepository
{
public function getById($id): Post;
public function save(Post $post);
public function delete($id);
}Međutim, u stvarnim projektima, ako se odluči koristiti repozitorije, često im se dodaju metode za preuzimanje zapisa:
<?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 se metode mogu implementirati putem Eloquent opsega, ali preopteretiti klase entiteta odgovornostima preuzimanja samih sebe — nije baš najbolja zamisao i premještanje te odgovornosti u klase repozitorija djeluje logično. Je li to tako? Posebno sam vizualno podijelio ovaj sučelje na dva dijela. Prvi dio metoda će biti korišten u operacijama zapisivanja.
Standardne operacije zapisivanja su:
- konstruiranje novog objekta i poziv PostRepository::save
- PostRepository::getById, manipulacije s entitetom i poziv PostRepository::save
- poziv PostRepository::delete
U operacijama zapisivanja ne koriste se metode preuzimanja. U operacijama čitanja koriste se samo metode get*. Ako pročitate o Principu razdvajanja sučelja (slovo I u SOLID), bit će jasno da je naše sučelje postalo preveliko i ispunjava najmanje dvije različite odgovornosti. Vrijeme je da ga podijelimo na dva. Metoda getById neophodna je u oba, no s komplikacijama aplikacije njene implementacije će biti različite. To ćemo vidjeti malo kasnije. O beskorisnosti write dijela sam pisao u prošlom članku, stoga ću ga ovdje jednostavno zaboraviti.
Read dijelovi mi se ne čine tako beskorisni, jer čak i za Eloquent ovdje može biti nekoliko implementacija. Kako nazvati klasu? Može ReadPostRepository, ali s predloškom Repository ima već malo veze. Može jednostavno PostQueries:
<?php
interface PostQueries
{
public function getById($id): Post;
public function getLastPosts();
public function getTopPosts();
public function getUserPosts($userId);
}Njegova implementacija putem Eloquent-a je prilično jednostavna:
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 mora biti povezan s implementacijom, na primjer u AppServiceProvider:
app->bind(PostQueries::class,
EloquentPostQueries::class);
}
}Ova klasa je već korisna. Ona izvršava svoju odgovornost, rasterećujući time ili kontrolere ili klasu entiteta. U kontroleru može biti korištena ovako:
$postQueries->getLastPosts(),
]);
}
} Metoda PostsController::lastPosts jednostavno traži neku implementaciju za sebe PostsQueries i radi s njom. U provajderu smo povezali PostQueries s klasom EloquentPostQueries i u kontroler će biti umetnuta ta klasa.
Zamislimo da je naša aplikacija postala vrlo popularna. Tisuće korisnika u minuti otvaraju stranicu s najnovijim objavama. Najpopularnije objave također se često čitaju. Baze podataka ne podnose dobro takva opterećenja, pa se koristi standardno rješenje - cache. Pored baze podataka, neki presjek podataka pohranjuje se u skladištu optimiziranom za određene operacije - memcached ili redis.
Logika keširanja obično nije toliko složena, ali implementirati je u EloquentPostQueries nije baš ispravno (barem zbog Single Responsibility Principle). Puno prirodnije je koristiti obrazac Dekorator i implementirati keširanje kao dekoriranje glavne funkcije:
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();
});
}
// ostale metode su praktički iste
}Ne obraćajte pažnju na sučelje Repository u konstruktoru. Iz nepoznatog razloga, tako su odlučili nazvati sučelje za keširanje u Laravelu.
Klasa CachedPostQueries realizira samo keširanje. $this->cache->remember provjerava postoji li ta stavka u kešu i ako ne postoji, poziva callback i zapisuje u keš vrijednost koja se vratila. Ostalo je samo implementirati ovu klasu u aplikaciju. Potrebno je da sve klase koje u aplikaciji traže implementaciju sučelja PostQueries počnu primati instancu klase CachedPostQueries. Međutim, sam CachedPostQueries kao parametar u konstruktor mora dobiti klasu EloquentPostQueries, budući da ne može raditi bez "prave" implementacije. Mijenjamo AppServiceProvider:
app->bind(PostQueries::class,
CachedPostQueries::class);
$this->app->when(CachedPostQueries::class)
->needs(PostQueries::class)
->give(EloquentPostQueries::class);
}
}Sve moje želje prilično su prirodno opisane u provajderu. Tako smo implementirali keširanje za naše upite samo pisanjem jedne klase i promjenom konfiguracije kontejnera. Kod ostatka aplikacije nije se promijenio.
Naravno, za potpunu implementaciju keširanja potrebno je još implementirati invalidaciju, kako bi uklonjena stavka odmah nestala sa stranice, a ne ostala neko vrijeme. Ali to su već sitnice.
Zaključak: koristili smo ne jedan, već dva obrasca. Obrazac Command Query Responsibility Segregation (CQRS) predlaže potpuno odvajanje operacija čitanja i pisanja na razini sučelja. Došao sam do njega kroz Principu razdvajanja sučelja, što pokazuje da vješto manipuliram obrascima i principima i izvodim jedan iz drugog kao teoremu 🙂 Naravno, ne mora svakom projektu biti potrebna takva apstrakcija za pretragu entiteta, ali ću podijeliti s vama trik. U početnoj fazi razvoja aplikacije, jednostavno možete stvoriti klasu PostQueries s uobičajenom implementacijom kroz Eloquent:
<?php
final class PostQueries
{
public function getById($id): Post
{
return Post::findOrFail($id);
}
// druge metode
}Kada se pojavi potreba za keširanjem, jednostavnim pokretom možete stvoriti sučelje (ili apstraktnu klasu) umjesto ove klase PostQueries, njegovu implementaciju kopirati u klasu EloquentPostQueries i preći na shemu koju sam opisao ranije. Ostali kod aplikacije ne treba mijenjati.
Sve te trikove s klasama, sučeljima, Dependency Injection i CQRS detaljno opisujem u . Tamo je i rješenje zagonetke zašto su sve moje klase u primjerima ovog članka označene kao final.
Izvor: habr.com
