Nyttige depoter med Eloquent?

Forrige uke skrev jeg artikkel om ubrukeligheten av Repository-malen for veltalende enheter, men han lovet å fortelle meg hvordan jeg delvis kan bruke det til sin fordel. For å gjøre dette vil jeg prøve å analysere hvordan denne malen vanligvis brukes i prosjekter. Minimum påkrevde sett med metoder for et depot:

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

Imidlertid, i virkelige prosjekter, hvis det ble bestemt å bruke depoter, blir metoder for å hente poster ofte lagt 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 metodene kan implementeres gjennom Eloquent-omfang, men å overbelaste entitetsklasser med ansvaret for å hente seg selv er ikke den beste ideen, og å flytte dette ansvaret til depotklasser virker logisk. Er det sånn? Jeg delte spesifikt visuelt dette grensesnittet i to deler. Den første delen av metodene vil bli brukt i skriveoperasjoner.

Standard skriveoperasjon er:

  • bygging av nytt objekt og utfordring PostRepository::lagre
  • PostRepository::getById, enhetsmanipulasjon og innkalling PostRepository::lagre
  • utfordringen PostRepository::delete

Skriveoperasjoner bruker ikke hentemetoder. I leseoperasjoner brukes kun get*-metoder. Hvis du leser om Grensesnittsegregeringsprinsipp (brev I в SOLID), så vil det bli klart at grensesnittet vårt er for stort og utfører minst to forskjellige oppgaver. Det er på tide å dele det på to. Metode getById er nødvendig i begge, men etter hvert som applikasjonen blir mer kompleks, vil implementeringen avvike. Dette får vi se litt senere. Jeg skrev om ubrukeligheten av skrivedelen i en tidligere artikkel, så i denne vil jeg rett og slett glemme det.

Les-delen virker for meg ikke så ubrukelig, siden selv for Eloquent kan det være flere implementeringer her. Hva skal klassen hete? Kan ReadPostRepository, men til malen Oppbevaringssted han har allerede liten relevans. Du kan bare PostQueries:

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

Å implementere det med Eloquent er ganske enkelt:

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

Grensesnittet må være tilknyttet implementeringen, for eksempel i AppServiceProvider:

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

Denne klassen er allerede nyttig. Han innser sitt ansvar ved å losse enten kontrollørene eller enhetsklassen. I en kontroller kan den brukes slik:

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

metode PostsController::lastposts ber bare om litt implementering PostsQueries og jobber med det. I leverandøren vi linket PostQueries med klasse EloquentPostQueries og denne klassen vil bli erstattet med kontrolleren.

La oss forestille oss at applikasjonen vår har blitt veldig populær. Tusenvis av brukere per minutt åpner siden med de siste publikasjonene. De mest populære publikasjonene leses også veldig ofte. Databaser takler ikke slike belastninger så godt, så de bruker en standardløsning - en cache. I tillegg til databasen, lagres et bestemt data øyeblikksbilde i lagring optimalisert for visse operasjoner - memcached eller Redis.

Bufringslogikk er vanligvis ikke så komplisert, men å implementere den i EloquentPostQueries er ikke særlig riktig (om bare fordi Prinsipp om enkeltansvar). Det er mye mer naturlig å bruke en mal Dekoratør og implementer caching som dekorasjon for 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 grensesnittet Oppbevaringssted i konstruktøren. Av en eller annen ukjent grunn bestemte de seg for å navngi grensesnittet for caching i Laravel på denne måten.

Klasse CachedPostQueries implementerer kun caching. $this->cache->husk sjekker om denne oppføringen er i hurtigbufferen og hvis ikke, ringer tilbakeringing og skriver den returnerte verdien til hurtigbufferen. Alt som gjenstår er å implementere denne klassen i applikasjonen. Vi trenger alle klasser som i applikasjonen for å be om en implementering av grensesnittet PostQueries begynte å motta en forekomst av klassen CachedPostQueries. Imidlertid han selv CachedPostQueries Konstruktøren må motta en klasse som en parameter EloquentPostQueriessiden det ikke kan fungere uten en "ekte" implementering. Vi forandrer oss 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 naturlig beskrevet i leverandøren. Dermed implementerte vi caching for våre forespørsler bare ved å skrive en klasse og endre beholderkonfigurasjonen. Resten av applikasjonskoden er ikke endret.

Selvfølgelig, for å implementere caching fullt ut, er det også nødvendig å implementere ugyldiggjøring slik at den slettede artikkelen ikke henger på nettstedet på en stund, men blir slettet umiddelbart. Men dette er småting.

Bunnlinjen: vi brukte ikke én, men to maler. Prøve Command Query Responsibility Segregation (CQRS) foreslår å fullstendig skille lese- og skriveoperasjoner på grensesnittnivå. Jeg kom til ham gjennom Grensesnittsegregeringsprinsipp, noe som antyder at jeg dyktig manipulerer mønstre og prinsipper og utleder det ene fra det andre som et teorem :) Selvfølgelig trenger ikke alle prosjekter en slik abstraksjon for å velge enheter, men jeg vil dele trikset med deg. På den innledende fasen av søknaden utvikling, kan du ganske enkelt opprette en klasse PostQueries med vanlig implementering via Eloquent:

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

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

Når behovet for caching oppstår, kan du med et enkelt trekk lage et grensesnitt (eller abstrakt klasse) i stedet for denne klassen PostQueries, kopier implementeringen til klassen EloquentPostQueries og gå til ordningen jeg beskrev tidligere. Resten av applikasjonskoden trenger ikke å endres.

Alle disse triksene med klasser, grensesnitt, Avhengighetsinjeksjon и CQRS beskrevet i detalj i min bok "Arkitektur av komplekse webapplikasjoner". Det er også en løsning på gåten hvorfor alle klassene mine i eksemplene for denne artikkelen er merket som endelige.

Kilde: www.habr.com

Legg til en kommentar