Användbara förråd med Eloquent?

Förra veckan skrev jag artikel om värdelösheten i arkivmallen för vältalig entitet, dock lovade han att berätta för mig hur man delvis kan använda det till sin fördel. För att göra detta ska jag försöka analysera hur denna mall vanligtvis används i projekt. Den minsta nödvändiga uppsättningen metoder för ett arkiv:

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

Men i verkliga projekt, om det beslutades att använda arkiv, läggs metoder för att hämta poster ofta till 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);
}

Dessa metoder skulle kunna implementeras genom Eloquent scopes, men att överbelasta entitetsklasser med ansvaret att hämta sig själva är inte den bästa idén, och att flytta detta ansvar till förvarsklasser verkar logiskt. Är det så? Jag delade specifikt visuellt detta gränssnitt i två delar. Den första delen av metoderna kommer att användas i skrivoperationer.

Standardskrivoperationen är:

  • konstruktion av ett nytt objekt och utmaning PostRepository::spara
  • PostRepository::getById, enhetsmanipulation och kallelse PostRepository::spara
  • en utmaning PostRepository::delete

Skrivoperationer använder inte hämtningsmetoder. I läsoperationer används endast get*-metoder. Om du läser om Gränssnittssegregationsprincip (brev I в SOLID), då blir det tydligt att vårt gränssnitt är för stort och utför minst två olika ansvarsområden. Det är dags att dela det med två. Metod getById är nödvändigt i båda, men när applikationen blir mer komplex kommer dess implementeringar att skilja sig åt. Vi får se detta lite senare. Jag skrev om värdelösheten i skrivdelen i en tidigare artikel, så i den här kommer jag helt enkelt att glömma det.

Läsdelen förefaller mig inte så värdelös, eftersom även för Eloquent kan det finnas flera implementeringar här. Vad ska klassen heta? Burk ReadPostRepository, men till mallen förvaret han har redan liten relevans. Du kan bara PostQueries:

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

Att implementera det med Eloquent är ganska 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();
    }
}

Gränssnittet måste vara kopplat till implementeringen, till exempel i AppServiceProvider:

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

Den här klassen är redan användbar. Han inser sitt ansvar genom att lossa antingen kontrollanterna eller enhetsklassen. I en kontroller kan den användas så här:

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

metod PostsController::lastPosts ber bara om ett visst genomförande PostsQueries och jobbar med det. I leverantören vi länkade PostQueries med klass EloquentPostQueries och denna klass kommer att ersättas med styrenheten.

Låt oss föreställa oss att vår applikation har blivit mycket populär. Tusentals användare per minut öppnar sidan med de senaste publikationerna. De mest populära publikationerna läses också väldigt ofta. Databaser hanterar inte sådana belastningar särskilt bra, så de använder en standardlösning - en cache. Förutom databasen lagras en viss dataögonblicksbild i lagring optimerad för vissa operationer - memcached eller redis.

Cachinglogik är vanligtvis inte så komplicerat, men att implementera den i EloquentPostQueries är inte särskilt korrekt (om bara för att Enkeltansvarsprincip). Det är mycket mer naturligt att använda en mall Dekoratör och implementera caching som dekoration för huvudåtgärden:

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

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

Ignorera gränssnittet förvaret i konstruktören. Av någon okänd anledning bestämde de sig för att namnge gränssnittet för cachning i Laravel på detta sätt.

Klass CachadPostQueries implementerar endast cachning. $this->cache->kom ihåg kontrollerar om denna post finns i cachen och om inte, ringer man tillbaka och skriver det returnerade värdet till cachen. Allt som återstår är att implementera denna klass i applikationen. Vi behöver alla klasser som finns i applikationen för att begära en implementering av gränssnittet PostQueries började ta emot en instans av klassen CachadPostQueries. Däremot han själv CachadPostQueries konstruktorn måste få en klass som en parameter EloquentPostQuerieseftersom det inte kan fungera utan en "riktig" implementering. Vi förändras 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);
    }
}

Alla mina önskemål är ganska naturligt beskrivna i leverantören. Således implementerade vi cachning för våra förfrågningar endast genom att skriva en klass och ändra containerkonfigurationen. Resten av applikationskoden har inte ändrats.

Naturligtvis, för att fullt ut implementera caching, är det också nödvändigt att implementera ogiltigförklaring så att den borttagna artikeln inte hänger på webbplatsen under en tid, utan raderas omedelbart. Men det här är mindre saker.

Sammanfattning: vi använde inte en, utan två mallar. Prov Command Query Responsibility Segregation (CQRS) föreslår att helt separera läs- och skrivoperationer på gränssnittsnivå. Jag kom till honom genom Gränssnittssegregationsprincip, vilket antyder att jag skickligt manipulerar mönster och principer och härleder det ena från det andra som ett teorem :) Naturligtvis behöver inte alla projekt en sådan abstraktion för att välja enheter, men jag kommer att dela tricket med dig. I det inledande skedet av ansökan utveckling kan du helt enkelt skapa en klass PostQueries med den vanliga implementeringen via Eloquent:

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

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

När behovet av cachning uppstår kan du med ett enkelt drag skapa ett gränssnitt (eller abstrakt klass) i stället för denna klass PostQueries, kopiera dess implementering till klassen EloquentPostQueries och gå till schemat jag beskrev tidigare. Resten av applikationskoden behöver inte ändras.

Alla dessa knep med klasser, gränssnitt, Beroende på injektion и CQRS beskrivs i detalj i min bok "Arkitektur av komplexa webbapplikationer". Det finns också en lösning på gåtan varför alla mina klasser i exemplen för denna artikel markeras som slutgiltiga.

Källa: will.com

Lägg en kommentar