Корисни складишта со Eloquent?

Минатата недела напишав статија за бескорисноста на шаблонот Repository за елоквентни ентитети, сепак, ми вети дека ќе ми каже како делумно да го искористам во своја полза. За да го направам ова, ќе се обидам да анализирам како овој образец обично се користи во проекти. Минималниот потребен сет на методи за складиште:

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

Меѓутоа, во реални проекти, ако се одлучи да се користат складишта, честопати се додаваат методи за преземање записи:

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

Овие методи би можеле да се имплементираат преку Eloquent scopes, но преоптоварувањето на класите на ентитети со одговорност за преземање на самите себе не е најдобрата идеја, а преместувањето на оваа одговорност во класите на складиштето изгледа логично. Дали е така? Конкретно визуелно го поделив овој интерфејс на два дела. Првиот дел од методите ќе се користи во операциите за пишување.

Стандардна операција за пишување е:

  • изградба на нов објект и предизвик PostRepository::save
  • PostRepository::getById, манипулација со субјекти и повикување PostRepository::save
  • предизвик PostRepository::избриши

Операциите за запишување не користат методи за преземање. Во операциите за читање, се користат само методите get*. Ако читате за Принцип на сегрегација на интерфејс (писмо I в СОЛИД), тогаш ќе стане јасно дека нашиот интерфејс е преголем и извршува најмалку две различни одговорности. Време е да се подели на два. Метод getById е неопходно и во двете, но како што апликацијата станува посложена, нејзините имплементации ќе се разликуваат. Ова ќе го видиме малку подоцна. Пишував за бескорисноста на делот за пишување во претходната статија, така што во оваа едноставно ќе заборавам на тоа.

Делот Читај ми се чини не толку бескорисен, бидејќи дури и за Eloquent може да има неколку имплементации овде. Како да се именува класата? Може ReadPostRepository, но до шаблонот Репозиториум тој веќе има мала важност. Можеш само Постпрашања:

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

Спроведувањето со Eloquent е прилично едноставно:

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

Интерфејсот мора да биде поврзан со имплементацијата, на пример во AppServiceProvider:

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

Оваа класа е веќе корисна. Својата одговорност ја сфаќа така што ги ослободува или контролорите или класата на ентитетот. Во контролер може да се користи вака:

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

Метод PostsController::lastPosts само барајќи одредена имплементација Прашања за постови и работи со него. Во провајдерот што го поврзавме Постпрашања со класа EloquentPostQueries и оваа класа ќе биде заменета во контролорот.

Да замислиме дека нашата апликација стана многу популарна. Илјадници корисници во минута ја отвораат страницата со најновите публикации. Многу често се читаат и најпопуларните публикации. Базите на податоци не се справуваат многу добро со таквите оптоварувања, па затоа користат стандардно решение - кеш. Покрај базата на податоци, одредена снимка на податоци се чува во складиште оптимизирано за одредени операции - мемориран или редис.

Логиката за кеширање обично не е толку комплицирана, но нејзиното спроведување во EloquentPostQueries не е многу точно (ако само затоа што Принцип на единствена одговорност). Многу поприродно е да користите шаблон Декоратор и имплементирајте кеширање како декорација за главната акција:

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

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

Игнорирај го интерфејсот Репозиториум во конструкторот. Од некоја непозната причина, тие решија да го именуваат интерфејсот за кеширање во Ларавел на овој начин.

Класа CachedPostQueries имплементира само кеширање. $this->cache->запомнете проверува дали овој запис е во кешот и ако не, тогаш повикува назад и ја запишува вратената вредност во кешот. Останува само да се имплементира оваа класа во апликацијата. Ни требаат сите класи кои во апликацијата да побараат имплементација на интерфејсот Постпрашања почна да добива примерок од класот CachedPostQueries. Сепак, тој самиот CachedPostQueries конструкторот мора да добие класа како параметар EloquentPostQueriesбидејќи не може да работи без „вистинска“ имплементација. Се менуваме 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);
    }
}

Сите мои желби се сосема природно опишани во давателот. Така, имплементиравме кеширање за нашите барања само со пишување на една класа и менување на конфигурацијата на контејнерот. Остатокот од кодот на апликацијата не е променет.

Се разбира, за целосно имплементирање на кеширање, исто така е неопходно да се спроведе неважење, така што избришаната статија не виси на страницата некое време, туку веднаш се брише. Но, ова се ситни работи.

Крајна линија: не користевме еден, туку два шаблони. Пример Сегрегација на одговорност за барање на команди (CQRS) предлага целосно да се одделат операциите за читање и запишување на ниво на интерфејс. Дојдов до него преку Принцип на сегрегација на интерфејс, што сугерира вешто да манипулирам со шаблони и принципи и да извлечам еден од друг како теорема :) Секако, на секој проект не му е потребна ваква апстракција за избор на ентитети, но ќе го споделам трикот со вас.Во почетната фаза на примена развој, можете едноставно да креирате класа Постпрашања со вообичаената имплементација преку Eloquent:

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

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

Кога ќе се појави потреба за кеширање, со едноставен потег можете да креирате интерфејс (или апстрактна класа) наместо оваа класа Постпрашања, копирајте ја неговата имплементација во класата EloquentPostQueries и одете на шемата што ја опишав претходно. Остатокот од кодот на апликацијата не треба да се менува.

Сите овие трикови со класи, интерфејси, Инјектирање на зависност и CQRS детално се опишани во мојата книга „Архитектура на сложени веб-апликации“. Има и решение за загатката зошто сите мои часови во примерите за оваа статија се означени како конечни.

Извор: www.habr.com

Додадете коментар