Hasznos repozitóriumok az Eloquenthez?

A múlt héten írtam cikket az Eloquent entitásokhoz készült Repository minta haszontalanságáról,azonban megígértem, hogy elmondom, hogyan lehet részben hasznosan használni. Ehhez megpróbálom elemezni, hogyan szokták ezt a mintát alkalmazni a projektekben. A repository minimálisan szükséges metódusai:

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

Viszont a valós projektekben, ha a repositoryk használatát elhatározták, gyakran kerülnek be a lekérdezési metódusok is:

<?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 metódusokat Eloquent scope-okon keresztül is meg lehetne valósítani, de a saját magukat lekérdezésre kötelezni az entitásosztályokban — nem a legjobb ötlet, és ennek a felelősségét a repository osztályokba átvinni logikusnak tűnik. Így van ez? Szándékosan vizuálisan kettéosztottam ezt az interfészt. Az első rész metódusait írási műveletekben fogják felhasználni.

A standard írási műveletek a következők:

  • új objektum létrehozása és hívás PostRepository::save
  • PostRepository::getById, entitással való manipuláció és hívás PostRepository::save
  • hívás PostRepository::delete

Írási műveletekben nem használunk lekérdezési metódusokat. Az olvasási műveletekben viszont csak a get* metódusok kerülnek elő. Ha elolvassuk az Interface Segregation Principle (betű I be. SOLID), akkor világossá válik, hogy interfészünk túl nagy lett, és legalább két különböző felelősséget lát el. Ideje kettéosztani. A getById metódusra mindkettőben szükség van, de a bonyolultabb alkalmazás esetén a megvalósítások eltérőek lesznek. Ezt mindjárt látni fogjuk. A write rész haszontalanságáról a múltkori cikkben írtam, ezért ebben csak egyszerűen elfelejtem. A read rész viszont nem tűnik annyira haszontalannak, mivel itt az Eloquent számára is több megvalósítás létezhet. Hogyan nevezzük el az osztályt? Lehetne ReadPostRepository

, de a Repository mintához már nem sok köze van. Egyszerűen nevezzük PostQueries<?php interface PostQueries { public function getById($id): Post; public function getLastPosts(); public function getTopPosts(); public function getUserPosts($userId); } Az Eloquent segítségével történő megvalósítása meglehetősen egyszerű: mintához már nem sok köze van. Egyszerűen csak lehetne PostQueries:

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

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

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

Az interfésznek kapcsolódnia kell a megvalósításhoz, például itt: AppServiceProvider:

app->bind(PostQueries::class, 
            EloquentPostQueries::class);
    }
}

Ez az osztály már hasznos. Megvalósítja a saját felelősségét, ezzel tehermentesítve a kontrollereket vagy az entitás osztályokat. A kontrollereknél így használható:

$postQueries->getLastPosts(),
        ]);
    }
} 

Módszer PostsController::lastPosts egyszerűen kér egy megvalósítást magának PostsQueries és azzal dolgozik. A szolgáltatóban összekapcsoltuk PostQueries ezzel az osztályjal EloquentPostQueries és a kontrollerbe ez az osztály kerül behelyettesítésre.

Képzeljük el, hogy az alkalmazásunk nagyon népszerűvé vált. Ezrek nyitják meg egy percben a legfrissebb bejegyzések oldalát. A legnépszerűbb bejegyzések is nagyon gyakran olvasottak. Az adatbázisok nem tudnak jól megbirkózni ilyen terheléssel, ezért általános megoldást alkalmaznak — a cache-t. Az adatbázis mellett egy adatmásolatot tárolnak egy optimalizált tárolóban, amely bizonyos műveletekre van optimalizálva — memcached vagy redis.

A cache logikája általában nem olyan bonyolult, de nem helyénvaló megvalósítani az EloquentPostQueries-ben (legalábbis a Single Responsibility Principle miatt). Sokkal természetesebb a Decorator mintázatot alkalmazni, és a cache-elést a fő művelet dekorálásaként megvalósítani: Single Responsibility Principle) Sokkal természetesebb a Decorator mintázatot használni, és a cache-elést a fő művelet dekorálásában megvalósítani: Dekorátor 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(); }); }// más módszerek gyakorlatilag hasonlóak }

// más módszerek gyakorlatilag hasonlóak

Ne figyelj az interfészre Az Eloquent segítségével történő megvalósítása meglehetősen egyszerű: a konstruktorban. Értelmetlen okból így nevezték el a Laravel cache-elő interfészt.

Osztály CachedPostQueries csak a cache-elést valósítja meg. $this->cache->remember ellenőrzi, hogy a bejegyzés létezik-e a cache-ben, és ha nem, akkor meghívja a callback-et, és elmenti a cache-be a visszatérő értéket. Csak be kell építenünk ezt az osztályt az alkalmazásba. Szükségünk van arra, hogy az összes osztály, amely az alkalmazásban kéri az interfész megvalósítását PostQueries megkapja az osztály példányát CachedPostQueries. Azonban magának CachedPostQueries paraméterként a konstruktornál meg kell kapnia az osztályt EloquentPostQueries, mivel „igazi” megvalósítás nélkül nem tud működni. Megváltoztatjuk AppServiceProvider:

app->bind(PostQueries::class, 
            CachedPostQueries::class);

        $this->app->when(CachedPostQueries::class)
            ->needs(PostQueries::class)
            ->give(EloquentPostQueries::class);
    }
}

Minden kívánságom viszonylag természetesen leírható a szolgáltatóban. Így a cache-elést az összes kérésünkhöz úgy valósítottuk meg, hogy csak egy osztályt írtunk és módosítottuk a konténer konfigurációját. A többi alkalmazáskód nem változott.

Természetesen a cache-elés teljes megvalósításához még implementálni kell az invalidálást, hogy a törölt cikk ne lógjon még a weboldalon egy ideig, és azonnal eltávolításra kerüljön. De ez már részletkérdés.

Összegzés: nem egy, hanem egy egész két mintát használtunk. A minta Command Query Responsibility Segregation (CQRS) teljesen javasolja az olvasási és írási műveletek szétválasztását az interfészek szintjén. Ehhez jutottam el, Interface Segregation Principle, ami azt jelzi, hogy ügyesen manőverezem a mintákat és elveket, és az egyiket a másikból vezetem le, mint egy tételt 🙂 Természetesen nem minden projekthez szükséges egy ilyen absztrakció az entitások lekérdezésére, de megosztom veletek a trükköt. Az alkalmazás fejlesztésének kezdeti szakaszában egyszerűen létrehozhatsz egy osztályt PostQueries a szokásos Eloquent megvalósítással:

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

    // más metódusok
}

Amikor szükséged lesz a cache-elésre, könnyedén létrehozhatsz egy interfészt (vagy absztrakt osztályt) ezen osztály helyén PostQueries, a megvalósítást átkopírozod az osztályba EloquentPostQueries és áttérsz a korábban leírt rendszerre. A többi alkalmazáskódot nem kell megváltoztatni.

Mindezek a trükkök az osztályok, interfészek, Dependency Injection és CQRS részletesen le vannak írva a könyvemben, 'Bonyolult webalkalmazások architektúrája'Itt van a rejtvény megoldása, hogy miért jelöltem meg az összes osztályt a cikk példáihoz final-nek.

Forrás: habr.com

Megbízható hosting vásárlása weboldalak számára, DDoS védelemmel, VPS VDS szerverek. 🔥 Megbízható webtárhely vásárlása DDoS védelemmel, VPS VDS szerverek | ProHoster