Hasznos adattárak az Eloquent segítségével?

Múlt héten írtam cikk a Repository sablon haszontalanságáról az Eloquent entitások számára, azonban megígérte, hogy elmondja, hogyan használhatom részben a javára. Ennek érdekében megpróbálom elemezni, hogyan használják ezt a sablont általában a projektekben. A minimálisan szükséges metóduskészlet egy adattárhoz:

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

A valós projektekben azonban, ha úgy döntöttek, hogy tárolókat használnak, gyakran hozzáadják a rekordok lekérésére szolgáló módszereket:

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

Ezeket a módszereket az Eloquent hatókörökön keresztül is meg lehet valósítani, de az entitásosztályok túlterhelése önmaguk lekérésének felelősségével nem a legjobb ötlet, és ennek a felelősségnek a tárosztályokra való áthelyezése logikusnak tűnik. így van? Ezt a felületet kifejezetten vizuálisan két részre osztottam. A metódusok első részét írási műveletekben fogják használni.

A szabványos írási művelet a következő:

  • új objektum építése és kihívás PostRepository::save
  • PostRepository::getById, entitás manipuláció és megidézés PostRepository::save
  • kihívás PostRepository::delete

Az írási műveletek nem használnak lekérési módszereket. Az olvasási műveletekben csak a get* metódusok használatosak. Ha olvasol róla Interfész szegregációs elve (levél I в SZILÁRD), akkor világossá válik, hogy a felületünk túl nagy, és legalább két különböző feladatot lát el. Ideje kettéosztani. Módszer getById mindkettőben szükséges, de az alkalmazás bonyolultabbá válásával a megvalósítások eltérőek lesznek. Ezt egy kicsit később meglátjuk. Az írási rész haszontalanságáról egy korábbi cikkben írtam, így ebben egyszerűen elfelejtem.

A Read rész számomra nem tűnik haszontalannak, hiszen itt még az Eloquent számára is több megvalósítás lehet. Hogyan nevezzük az osztályt? Tud ReadPostRepository, hanem a sablonhoz raktár neki már nincs jelentősége. Csak lehet PostQuerys:

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

Megvalósítása az Eloquent segítségével meglehetősen egyszerű:

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

A felületet társítani kell a megvalósításhoz, például in AppServiceProvider:

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

Ez az osztály már hasznos. Felelősségét azáltal ismeri fel, hogy felmenti a vezérlőket vagy az entitásosztályt. Vezérlőben a következőképpen használható:

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

módszer PostsController::lastPosts csak valami megvalósítást kérek PostsQuerys és dolgozik vele. Az általunk összekapcsolt szolgáltatóban PostQuerys osztállyal EloquentPostQueries és ez az osztály be lesz cserélve a vezérlőbe.

Képzeljük el, hogy alkalmazásunk nagyon népszerűvé vált. Percenként több ezer felhasználó nyitja meg a legújabb kiadványokat tartalmazó oldalt. A legnépszerűbb kiadványokat is nagyon gyakran olvassák. Az adatbázisok nem nagyon kezelik az ilyen terheléseket, ezért szabványos megoldást használnak - a gyorsítótárat. Az adatbázison kívül bizonyos műveletekre optimalizált tárolóban tárolunk egy bizonyos adat pillanatképet - memcached vagy Redis.

A gyorsítótárazási logika általában nem olyan bonyolult, de az EloquentPostQueries-ben való megvalósítás nem túl helyes (már csak azért is, mert Egyetlen felelősség elve). Sokkal természetesebb a sablon használata Lakberendező és alkalmazza a gyorsítótárat a fő művelet dekorációjaként:

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

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

Figyelmen kívül hagyja a felületet raktár a konstruktorban. Ismeretlen okból úgy döntöttek, hogy így nevezik el a Laravel gyorsítótárazási felületét.

Osztály CachedPostQueries csak gyorsítótárat valósít meg. $ez->gyorsítótár->emlékezzen ellenőrzi, hogy ez a bejegyzés a gyorsítótárban van-e, és ha nincs, akkor visszahívja, és a visszaadott értéket beírja a gyorsítótárba. Már csak ezt az osztályt kell implementálni az alkalmazásba. Szükségünk van minden olyan osztályra, amely az alkalmazásban kéri a felület megvalósítását PostQuerys kezdett kapni egy példányt az osztályból CachedPostQueries. Azonban ő maga CachedPostQueries a konstruktornak egy osztályt kell kapnia paraméterként EloquentPostQuerieshiszen "igazi" megvalósítás nélkül nem működhet. Változunk 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);
    }
}

Minden kívánságomat teljesen természetesen leírja a szolgáltató. Így kéréseink gyorsítótárazását csak egy osztály írásával és a tároló konfigurációjának megváltoztatásával valósítottuk meg. Az alkalmazáskód többi része nem változott.

Természetesen a gyorsítótárazás teljes megvalósításához az érvénytelenítést is végre kell hajtani, hogy a törölt cikk ne lógjon egy ideig az oldalon, hanem azonnal törlődik. De ezek apróságok.

A lényeg: nem egy, hanem két sablont használtunk. Minta Command Query Responsibility Segregation (CQRS) javasolja az olvasási és írási műveletek teljes elkülönítését az interfész szintjén. keresztül jöttem hozzá Interfész szegregációs elve, ami azt sugallja, hogy ügyesen manipulálok mintákat és elveket, és egyiket a másikból levezetem tételként :) Természetesen nem minden projektben kell ilyen absztrakció az entitások kiválasztásához, de megosztom veletek a trükköt. Az alkalmazás kezdeti szakaszában fejlesztést, egyszerűen létrehozhat egy osztályt PostQuerys a szokásos megvalósítással az Eloquent-en keresztül:

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

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

Ha gyorsítótárazásra van szükség, egy egyszerű mozdulattal létrehozhat egy felületet (vagy absztrakt osztályt) az osztály helyett. PostQuerys, másolja a megvalósítását az osztályba EloquentPostQueries és lépjen a korábban leírt sémára. Az alkalmazáskód többi részét nem kell módosítani.

Mindezek a trükkök osztályokkal, felületekkel, Függőség-befecskendezés и CQRS fejezetében részletesen le van írva a „Komplex webalkalmazások architektúrája” című könyvem. Van megoldás arra a rejtvényre is, hogy a cikk példáiban miért van minden osztályom véglegesnek jelölve.

Forrás: will.com

Hozzászólás