Gagnlegar geymslur með Eloquent?

Í síðustu viku skrifaði ég grein um gagnsleysi Repository sniðmátsins fyrir Eloquent aðilaHins vegar lofaði hann að segja mér hvernig ætti að nota það að hluta til hans hagsbóta. Til að gera þetta mun ég reyna að greina hvernig þetta sniðmát er venjulega notað í verkefnum. Lágmarks sem krafist er af aðferðum fyrir geymslu:

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

Hins vegar, í raunverulegum verkefnum, ef ákveðið var að nota geymslur, er aðferðum til að sækja skrár oft bætt við þær:

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

Þessar aðferðir gætu verið útfærðar í gegnum Eloquent svigrúm, en ofhleðsla einingaflokka með þá ábyrgð að sækja sjálfa sig er ekki besta hugmyndin og að færa þessa ábyrgð yfir á geymsluflokka virðist rökrétt. Er það svo? Ég skipti þessu viðmóti sérstaklega sjónrænt í tvo hluta. Fyrsti hluti aðferðanna verður notaður í ritaðgerðum.

Hefðbundin skrifaðgerð er:

  • smíði á nýjum hlut og áskorun PostRepository::vista
  • PostRepository::getById, meðhöndlun aðila og boðun PostRepository::vista
  • áskorunin PostRepository::eyða

Skrifunaraðgerðir nota ekki niðurhalsaðferðir. Í lestraraðgerðum eru aðeins get* aðferðir notaðar. Ef þú lest um Regla aðskilnaðar viðmóts (bréf I в SOLID), þá verður ljóst að viðmótið okkar er of stórt og sinnir að minnsta kosti tveimur mismunandi skyldum. Það er kominn tími til að deila því með tveimur. Aðferð getById er nauðsynlegt í báðum, en eftir því sem forritið verður flóknara verða útfærslur hennar mismunandi. Við sjáum þetta aðeins síðar. Ég skrifaði um gagnsleysi skrifa hlutans í fyrri grein, svo í þessari mun ég einfaldlega gleyma því.

Leshlutinn virðist mér ekki svo gagnslaus, þar sem jafnvel fyrir Eloquent geta verið nokkrar útfærslur hér. Hvað á að nefna bekkinn? Dós ReadPostRepository, en að sniðmátinu Geymsla hann hefur nú þegar litla þýðingu. Þú getur bara PostQueries:

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

Það er frekar einfalt að útfæra það með Eloquent:

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

Viðmótið verður að vera tengt útfærslunni, til dæmis í AppService Provider:

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

Þessi flokkur er nú þegar gagnlegur. Hann gerir sér grein fyrir ábyrgð sinni með því að leysa annað hvort stjórnendur eða einingaflokkinn af. Í stjórnanda er hægt að nota það svona:

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

Aðferð PostsController::lastPosts bara að biðja um einhverja útfærslu PostsQueries og vinnur með það. Í veitunni sem við tengdum PostQueries með bekk EloquentPostQueries og þessum flokki verður skipt út í stjórnandann.

Við skulum ímynda okkur að forritið okkar hafi orðið mjög vinsælt. Þúsundir notenda á mínútu opna síðuna með nýjustu útgáfum. Vinsælustu ritin eru líka lesin mjög oft. Gagnagrunnar höndla ekki slíkt álag mjög vel, svo þeir nota staðlaða lausn - skyndiminni. Til viðbótar við gagnagrunninn er ákveðin gagnamynd geymd í geymslu sem er fínstillt fyrir ákveðnar aðgerðir - skyndiminni eða endurtaka.

Skyndiminnisrökfræði er venjulega ekki svo flókin, en útfærsla á því í EloquentPostQueries er ekki mjög rétt (þó ekki nema vegna þess að Meginregla um eina ábyrgð). Það er miklu eðlilegra að nota sniðmát Skreyttari og innleiða skyndiminni sem skraut fyrir aðalaðgerðina:

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

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

Hunsa viðmótið Geymsla í byggingaraðilanum. Af einhverjum óþekktum ástæðum ákváðu þeir að nefna viðmótið fyrir skyndiminni í Laravel með þessum hætti.

Class CachedPostQueries útfærir aðeins skyndiminni. $this->cache->mundu athugar hvort þessi færsla sé í skyndiminni og ef ekki, þá hringir afturkall og skrifar skilað gildi í skyndiminni. Allt sem er eftir er að innleiða þennan flokk í forritið. Við þurfum alla flokka sem í forritinu til að biðja um útfærslu á viðmótinu PostQueries byrjaði að fá dæmi um bekkinn CachedPostQueries. Hins vegar sjálfur CachedPostQueries smiðurinn verður að fá flokk sem færibreytu EloquentPostQueriesþar sem það getur ekki virkað án "alvöru" útfærslu. Við breytum AppService Provider:

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

Allar óskir mínar eru eðlilega lýst í þjónustuveitunni. Þannig útfærðum við skyndiminni fyrir beiðnir okkar aðeins með því að skrifa einn flokk og breyta gámastillingunni. Restin af forritskóðanum hefur ekki breyst.

Auðvitað, til að innleiða skyndiminni að fullu, er einnig nauðsynlegt að innleiða ógildingu þannig að eytt grein hangi ekki á síðunni í nokkurn tíma, heldur sé eytt strax. En þetta eru smáatriði.

Niðurstaða: við notuðum ekki eitt, heldur tvö sniðmát. Sýnishorn Skipunarfyrirspurnarábyrgðaraðgreining (CQRS) leggur til að les- og skrifaðgerðir verði algjörlega aðskildar á viðmótsstigi. Ég kom til hans í gegnum Regla aðskilnaðar viðmóts, sem bendir til þess að ég hagnýti hæfileika mynstur og lögmál og leiði hvert af öðru sem setningu :) Auðvitað þarf ekki hvert verkefni slíka abstrakt til að velja einingar, en ég mun deila bragðinu með þér. Á upphafsstigi umsóknar þróun, getur þú einfaldlega búið til bekk PostQueries með venjulegri útfærslu í gegnum Eloquent:

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

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

Þegar þörfin fyrir skyndiminni kemur upp geturðu með einfaldri hreyfingu búið til viðmót (eða abstrakt flokk) í stað þessa flokks PostQueries, afritaðu framkvæmd þess í bekkinn EloquentPostQueries og farðu í kerfið sem ég lýsti áðan. Það sem eftir er af forritskóðanum þarf ekki að breyta.

Öll þessi brellur með flokkum, viðmótum, Háð innspýting и CQRS lýst ítarlega í bókin mín „Architecture of Complex Web Applications“. Það er líka lausn á gátunni hvers vegna allir bekkirnir mínir í dæmunum fyrir þessa grein eru merktir sem endanlegir.

Heimild: www.habr.com

Bæta við athugasemd