Naudingos saugyklos su „Iloquent“?

Praėjusią savaitę rašiau straipsnis apie saugyklos šablono nenaudingumą iškalbingiems subjektams, tačiau jis pažadėjo man pasakyti, kaip iš dalies panaudoti tai savo naudai. Norėdami tai padaryti, pabandysiu išanalizuoti, kaip šis šablonas paprastai naudojamas projektuose. Minimalus reikalaujamas saugyklos metodų rinkinys:

<?php
interface PostRepository
{
    public function getById($id): Post;
    public function save(Post $post);
    public function delete($id);
}

Tačiau tikruose projektuose, jei buvo nuspręsta naudoti saugyklas, prie jų dažnai pridedami įrašų gavimo metodai:

<?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);
}

Šiuos metodus būtų galima įgyvendinti per Eloquent apimtis, tačiau perkrauti objektų klases, atsakingomis už jų atsiėmimą, nėra geriausia idėja, todėl šios atsakomybės perkėlimas į saugyklų klases atrodo logiška. Ar taip yra? Aš specialiai vizualiai padalinau šią sąsają į dvi dalis. Pirmoji metodų dalis bus naudojama rašymo operacijose.

Standartinė rašymo operacija yra tokia:

  • naujo objekto statyba ir iššūkis PostRepository::save
  • PostRepository::getById, subjekto manipuliavimas ir iškvietimas PostRepository::save
  • iššūkis PostRepository::delete

Rašymo operacijos nenaudoja gavimo metodų. Skaitymo operacijose naudojami tik get* metodai. Jei skaitote apie Sąsajos atskyrimo principas (laiškas I в SOLID), tada paaiškės, kad mūsų sąsaja yra per didelė ir atlieka bent dvi skirtingas pareigas. Atėjo laikas padalyti jį iš dviejų. Metodas getById yra būtinas abiem atvejais, tačiau programai tampant sudėtingesnei, jos įgyvendinimas skirsis. Tai pamatysime šiek tiek vėliau. Apie rašymo dalies nenaudingumą rašiau ankstesniame straipsnyje, todėl šiame tiesiog apie tai pamiršiu.

Skaitymo dalis man atrodo ne tokia nenaudinga, nes net „Eloquent“ čia gali būti keletas įgyvendinimų. Kaip pavadinti klasę? Gali ReadPostRepository, bet prie šablono Saugykla jis jau mažai aktualus. Galite tiesiog PostQuerys:

<?php
interface PostQueries
{
    public function getById($id): Post;
    public function getLastPosts();
    public function getTopPosts();
    public function getUserPosts($userId);
}

Ją įgyvendinti naudojant „Eloquent“ yra gana paprasta:

<?php
final class EloquentPostQueries implements PostQueries
{
    public function getById($id): Post
    {
        return Post::findOrFail($id);
    }

    /**
    * @return Post[] | Collection
    */
    public function getLastPosts()
    {
        return Post::orderBy('created_at', 'desc')
            ->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();
    }
}

Sąsaja turi būti susieta su įgyvendinimu, pvz AppServiceProvider:

<?php
final class AppServiceProvider extends ServiceProvider 
{
    public function register()
    {
        $this->app->bind(PostQueries::class, 
            EloquentPostQueries::class);
    }
}

Ši klasė jau naudinga. Jis suvokia savo atsakomybę atleisdamas arba valdytojus, arba subjektų klasę. Valdiklyje jis gali būti naudojamas taip:

<?php
final class PostsController extends Controller
{
    public function lastPosts(PostQueries $postQueries)
    {
        return view('posts.last', [
            'posts' => $postQueries->getLastPosts(),
        ]);
    }
} 

metodas PostsController::lastPosts tik prašau kažkokio įgyvendinimo PostsQuerys ir dirba su juo. Mes susietame teikėje PostQuerys su klase „IloquentPostQueries“. ir ši klasė bus pakeista į valdiklį.

Įsivaizduokime, kad mūsų programa tapo labai populiari. Tūkstančiai vartotojų per minutę atidaro puslapį su naujausiais leidiniais. Populiariausi leidiniai taip pat skaitomi labai dažnai. Duomenų bazės nelabai gerai susitvarko su tokiais apkrovimais, todėl naudoja standartinį sprendimą – talpyklą. Be duomenų bazės, tam tikra duomenų momentinė nuotrauka saugoma tam tikroms operacijoms optimizuotoje saugykloje - memcached arba Redis.

Talpyklos logika paprastai nėra tokia sudėtinga, tačiau ją įdiegti EloquentPostQueries nėra labai teisinga (jei tik todėl Vienos atsakomybės principas). Daug natūraliau naudoti šabloną Dekoratorė ir įgyvendinti talpyklą kaip pagrindinio veiksmo puošmeną:

<?php
use IlluminateContractsCacheRepository;

final class CachedPostQueries implements PostQueries
{
    const LASTS_DURATION = 10;

    /** @var PostQueries */
    private $base;

    /** @var Repository */
    private $cache;

    public function __construct(
        PostQueries $base, Repository $cache) 
    {
        $this->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();
            });
    }

    // другие методы практически такие же
}

Nepaisykite sąsajos Saugykla konstruktoriuje. Dėl kažkokios nežinomos priežasties jie nusprendė taip pavadinti Laravel talpyklos sąsają.

Klasė CachedPostQueries įgyvendina tik talpyklą. $this->cache->atminkite patikrina, ar šis įrašas yra talpykloje, o jei ne, tada skambina atgal ir įrašo grąžintą reikšmę į talpyklą. Lieka tik įdiegti šią klasę programoje. Mums reikalingos visos programoje esančios klasės, kad prašytume įdiegti sąsają PostQuerys pradėjo gauti klasės egzempliorių CachedPostQueries. Tačiau jis pats CachedPostQueries konstruktorius turi gauti klasę kaip parametrą „IloquentPostQueries“.nes jis negali veikti be „tikro“ įgyvendinimo. Keičiamės AppServiceProvider:

<?php
final class AppServiceProvider extends ServiceProvider 
{
    public function register()
    {
        $this->app->bind(PostQueries::class, 
            CachedPostQueries::class);

        $this->app->when(CachedPostQueries::class)
            ->needs(PostQueries::class)
            ->give(EloquentPostQueries::class);
    }
}

Visi mano norai yra gana natūraliai aprašyti teikėje. Taigi mes įdiegėme savo užklausų talpyklą tik parašydami vieną klasę ir pakeisdami konteinerio konfigūraciją. Likusi programos kodas nepasikeitė.

Žinoma, norint visiškai įdiegti talpyklą, taip pat būtina įdiegti negaliojimą, kad ištrintas straipsnis kurį laiką nekabėtų svetainėje, o būtų nedelsiant ištrintas. Bet tai smulkmenos.

Apatinė eilutė: naudojome ne vieną, o du šablonus. Pavyzdys Komandų užklausos atsakomybės atskyrimas (CQRS) siūlo visiškai atskirti skaitymo ir rašymo operacijas sąsajos lygiu. Aš atėjau pas jį per Sąsajos atskyrimo principas, o tai rodo, kad aš sumaniai manipuliuoju šablonais ir principais ir kaip teoremą išvedau vieną iš kito :) Žinoma, ne kiekvienam projektui reikia tokios abstrakcijos renkantis esybes, bet aš pasidalinsiu gudrybe su jumis. Pradiniame taikymo etape plėtrą, galite tiesiog sukurti klasę PostQuerys su įprastu įgyvendinimu per Eloquent:

<?php
final class PostQueries
{
    public function getById($id): Post
    {
        return Post::findOrFail($id);
    }

    // другие методы
}

Kai atsiranda talpyklos saugojimo poreikis, paprastu judesiu galite sukurti sąsają (arba abstrakčią klasę) vietoj šios klasės PostQuerys, nukopijuokite jo įgyvendinimą į klasę „IloquentPostQueries“. ir pereikite prie anksčiau aprašytos schemos. Likusio programos kodo keisti nereikia.

Visi šie triukai su klasėmis, sąsajomis, Priklausomybės injekcija и CQRS yra išsamiai aprašyti mano knyga „Sudėtingų žiniatinklio programų architektūra“. Taip pat yra mįslės sprendimas, kodėl visos mano klasės šio straipsnio pavyzdžiuose yra pažymėtos kaip baigiamos.

Šaltinis: www.habr.com

Добавить комментарий