Kasulikud hoidlad rakendusega Eloquent?

Eelmisel nädalal kirjutasin artikkel Repository malli kasutusest kõnekate üksuste jaoks, aga lubas ta mulle öelda, kuidas seda osaliselt enda kasuks ära kasutada. Selleks proovin analüüsida, kuidas seda malli tavaliselt projektides kasutatakse. Hoidla jaoks nõutav minimaalne meetodite komplekt:

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

Kui aga reaalsetes projektides otsustati kasutada hoidlaid, lisatakse neile sageli kirjete allalaadimise meetodid:

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

Neid meetodeid saab rakendada Eloquent ulatuse kaudu, kuid olemiklasside ülekoormamine enda toomise kohustusega pole parim idee ja selle vastutuse teisaldamine hoidlaklassidesse tundub loogiline. On see nii? Jagasin selle liidese konkreetselt visuaalselt kaheks osaks. Meetodite esimest osa kasutatakse kirjutamisoperatsioonides.

Standardne kirjutamisoperatsioon on järgmine:

  • uue objekti ehitamine ja väljakutse PostRepository::save
  • PostRepository::getById, üksuse manipuleerimine ja väljakutsumine PostRepository::save
  • väljakutse PostRepository::delete

Kirjutamistoimingud ei kasuta toomismeetodeid. Lugemistoimingutes kasutatakse ainult get* meetodeid. Kui loed umbes Liidese eraldamise põhimõte (kiri I в SOLID), siis selgub, et meie liides on liiga suur ja täidab vähemalt kahte erinevat ülesannet. On aeg see kahega jagada. meetod getById on vajalik mõlemas, kuid rakenduse muutudes keerukamaks, erinevad selle rakendused. Seda näeme veidi hiljem. Kirjutasin kirjutamisosa kasutusest eelmises artiklis, nii et selles artiklis unustan selle lihtsalt ära.

Loe osa ei tundu mulle nii kasutu, kuna isegi Eloquenti jaoks võib siin olla mitu rakendust. Kuidas klassile nime panna? Saab ReadPostRepository, vaid malli juurde Hoidla tal on niigi vähe tähtsust. Saate lihtsalt Postipäringud:

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

Selle rakendamine Eloquentiga on üsna lihtne:

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

Liides peab olema seotud juurutamisega, näiteks sisse AppServiceProvider:

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

See klass on juba kasulik. Ta mõistab oma vastutust, vabastades kas vastutavad töötlejad või olemiklassi. Kontrolleris saab seda kasutada järgmiselt:

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

Meetod PostsController::lastPosts lihtsalt küsin veidi rakendamist Postituspäringud ja töötab sellega. Meie lingitud pakkujas Postipäringud klassiga EloquentPostQueries ja see klass asendatakse kontrolleriga.

Kujutagem ette, et meie rakendus on muutunud väga populaarseks. Tuhanded kasutajad minutis avavad uusimate väljaannetega lehe. Väga sageli loetakse ka populaarsemaid väljaandeid. Andmebaasid ei tule selliste koormustega kuigi hästi toime, mistõttu kasutatakse standardlahendust – vahemälu. Lisaks andmebaasile salvestatakse teatud toimingute jaoks optimeeritud salvestusruumi teatud andmete hetktõmmis - mälukaart või redis.

Vahemällu salvestamise loogika pole tavaliselt nii keeruline, kuid selle rakendamine EloquentPostQueriesis pole kuigi õige (kui ainult sellepärast, et Ühe vastutuse põhimõte). Palju loomulikum on kasutada malli Kaunistaja ja rakendage vahemälu põhitegevuse kaunistuseks:

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

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

Ignoreeri liidest Hoidla konstruktoris. Mingil teadmata põhjusel otsustasid nad Laraveli vahemällu salvestamise liidese niimoodi nimetada.

Klass CachedPostQueries rakendab ainult vahemällu salvestamist. $see->vahemälu->mäleta kontrollib, kas see kirje on vahemälus ja kui ei, siis helistab tagasi ja kirjutab tagastatud väärtuse vahemällu. Jääb vaid see klass rakendusse juurutada. Liidese juurutamise taotlemiseks vajame kõiki rakenduse klasse Postipäringud hakkas saama klassi eksemplari CachedPostQueries. Samas ta ise CachedPostQueries konstruktor peab saama parameetrina klassi EloquentPostQuerieskuna see ei saa töötada ilma "päris" teostuseta. Me muutume 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);
    }
}

Kõik minu soovid on pakkujas üsna loomulikult kirjeldatud. Seega rakendasime oma päringute vahemällu ainult ühe klassi kirjutamise ja konteineri konfiguratsiooni muutmisega. Ülejäänud rakenduse kood pole muutunud.

Muidugi on vahemälu täielikuks rakendamiseks vaja rakendada ka kehtetuks tunnistamist, et kustutatud artikkel mõnda aega saidil ei ripuks, vaid kustutataks kohe. Kuid need on väikesed asjad.

Alumine rida: me kasutasime mitte ühte, vaid kahte malli. Näidis Command Query Responsibility Segregation (CQRS) teeb ettepaneku lugemis- ja kirjutamistoimingud liidese tasemel täielikult eraldada. Tulin tema juurde läbi Liidese eraldamise põhimõte, mis viitab sellele, et ma oskan mustrite ja põhimõtetega osavalt manipuleerida ning teoreemina üht teisest tuletada :) Muidugi ei vaja iga projekt üksuste valimiseks sellist abstraktsiooni, kuid jagan teiega seda nippi. Rakenduse algfaasis arendamiseks, saate lihtsalt klassi luua Postipäringud tavalise rakendamisega Eloquenti kaudu:

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

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

Kui tekib vajadus vahemällu salvestamise järele, saate lihtsa liigutusega luua selle klassi asemele liidese (või abstraktse klassi). Postipäringud, kopeerige selle rakendamine klassi EloquentPostQueries ja minge eelnevalt kirjeldatud skeemi juurde. Ülejäänud rakenduse koodi pole vaja muuta.

Kõik need nipid klasside, liidestega, Sõltuvuse süstimine и CQRS üksikasjalikult kirjeldatud minu raamat "Keeruliste veebirakenduste arhitektuur". Samuti on lahendus mõistatusele, miks kõik minu klassid selle artikli näidetes on märgitud lõplikuks.

Allikas: www.habr.com

Lisa kommentaar