Nyttige arkiver med Eloquent?

I sidste uge skrev jeg artikel om ubrugeligheden af ​​Repository-skabelonen for veltalende enheder, dog lovede han at fortælle mig, hvordan man delvist kunne bruge det til sin fordel. For at gøre dette vil jeg prøve at analysere, hvordan denne skabelon normalt bruges i projekter. Det minimumskrævede sæt af metoder til et lager:

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

Men i rigtige projekter, hvis det blev besluttet at bruge depoter, tilføjes metoder til at hente poster ofte til dem:

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

Disse metoder kunne implementeres gennem Eloquent scopes, men at overbelaste entitetsklasser med ansvaret for at hente sig selv er ikke den bedste idé, og at flytte dette ansvar til lagerklasser virker logisk. Er det sådan? Jeg opdelte specifikt visuelt denne grænseflade i to dele. Den første del af metoderne vil blive brugt i skriveoperationer.

Standard skriveoperationen er:

  • konstruktion af et nyt objekt og udfordring PostRepository::gem
  • PostRepository::getById, enhedsmanipulation og indkaldelse PostRepository::gem
  • en udfordring PostRepository::delete

Skriveoperationer bruger ikke hentemetoder. I læseoperationer bruges kun get*-metoder. Hvis du læser om Interfacesegregationsprincip (brev I в SOLID), så bliver det klart, at vores grænseflade er for stor og udfører mindst to forskellige ansvarsområder. Det er på tide at dividere det med to. Metode getById er nødvendigt i begge, men efterhånden som applikationen bliver mere kompleks, vil dens implementeringer afvige. Det vil vi se lidt senere. Jeg skrev om ubrugeligheden af ​​skrivedelen i en tidligere artikel, så i denne vil jeg simpelthen glemme det.

Læs-delen forekommer mig ikke så ubrugelig, da der selv for Eloquent kan være flere implementeringer her. Hvad skal klassen hedde? Kan ReadPostRepository, men til skabelonen Repository han har allerede ringe relevans. Du kan bare PostQueries:

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

Implementering af det med Eloquent er ret simpelt:

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

Interfacet skal være tilknyttet implementeringen, f.eks AppServiceProvider:

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

Denne klasse er allerede nyttig. Han indser sit ansvar ved at losse enten controllerne eller enhedsklassen. I en controller kan den bruges sådan her:

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

fremgangsmåde PostsController::sidste indlæg beder bare om en implementering IndlægForespørgsler og arbejder med det. I udbyderen vi linkede PostQueries med klasse EloquentPostQueries og denne klasse vil blive erstattet af controlleren.

Lad os forestille os, at vores applikation er blevet meget populær. Tusindvis af brugere i minuttet åbner siden med de seneste publikationer. De mest populære publikationer læses også meget ofte. Databaser håndterer ikke sådanne belastninger særlig godt, så de bruger en standardløsning - en cache. Ud over databasen gemmes et bestemt datasnapshot i et lager optimeret til bestemte operationer - memcached eller Redis.

Cachinglogik er normalt ikke så kompliceret, men at implementere den i EloquentPostQueries er ikke særlig korrekt (hvis kun fordi Princip for et enkelt ansvar). Det er meget mere naturligt at bruge en skabelon Dekoratør og implementer caching som dekoration til hovedhandlingen:

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

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

Ignorer grænsefladen Repository i konstruktøren. Af en eller anden ukendt årsag besluttede de at navngive grænsefladen til caching i Laravel på denne måde.

Klasse CachedPostQueries implementerer kun caching. $this->cache->husk kontrollerer, om denne post er i cachen, og hvis ikke, kalder den tilbage og skriver den returnerede værdi til cachen. Det eneste, der er tilbage, er at implementere denne klasse i applikationen. Vi har brug for alle klasser i applikationen for at anmode om en implementering af grænsefladen PostQueries begyndte at modtage en forekomst af klassen CachedPostQueries. Dog han selv CachedPostQueries konstruktøren skal modtage en klasse som en parameter EloquentPostQueriesda det ikke kan fungere uden en "rigtig" implementering. Vi ændrer os 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);
    }
}

Alle mine ønsker er ganske naturligt beskrevet i udbyderen. Således implementerede vi kun caching for vores anmodninger ved at skrive en klasse og ændre containerkonfigurationen. Resten af ​​applikationskoden er ikke ændret.

For fuldt ud at implementere caching er det selvfølgelig også nødvendigt at implementere ugyldiggørelse, så den slettede artikel ikke hænger på webstedet i nogen tid, men slettes med det samme. Men det er småting.

Nederste linje: vi brugte ikke én, men to skabeloner. Prøve Command Query Responsibility Segregation (CQRS) foreslår fuldstændig adskillelse af læse- og skriveoperationer på grænsefladeniveau. Jeg kom til ham igennem Interfacesegregationsprincip, hvilket tyder på, at jeg dygtigt manipulerer mønstre og principper og udleder det ene fra det andet som et teorem :) Selvfølgelig behøver ikke ethvert projekt en sådan abstraktion for at udvælge enheder, men jeg vil dele tricket med dig. I den indledende fase af ansøgningen udvikling, kan du blot oprette en klasse PostQueries med den sædvanlige implementering via Eloquent:

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

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

Når behovet for caching opstår, kan du med et enkelt træk oprette en grænseflade (eller abstrakt klasse) i stedet for denne klasse PostQueries, kopier dens implementering til klassen EloquentPostQueries og gå til den ordning, jeg beskrev tidligere. Resten af ​​applikationskoden skal ikke ændres.

Alle disse tricks med klasser, grænseflader, Afhængighed Injektion и CQRS beskrevet detaljeret i min bog "Arkitektur af komplekse webapplikationer". Der er også en løsning på gåden, hvorfor alle mine klasser i eksemplerne til denne artikel er markeret som endelige.

Kilde: www.habr.com

Tilføj en kommentar