Eloquent көмегімен пайдалы репозиторийлер?

Өткен аптада жаздым Eloquent нысандары үшін репозиторий үлгісінің пайдасыздығы туралы мақала, дегенмен ол маған оны жартылай өз пайдасына қалай пайдалану керектігін айтып беруге уәде берді. Мұны істеу үшін мен бұл үлгінің жобаларда қалай қолданылатынын талдауға тырысамын. Репозиторий үшін қажетті әдістердің ең аз жиынтығы:

<?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 ауқымдары арқылы жүзеге асыруға болады, бірақ субъект сыныптарын өздерін алу жауапкершілігімен шамадан тыс жүктеу ең жақсы идея емес және бұл жауапкершілікті репозиторий сыныптарына жылжыту қисынды болып көрінеді. Солай ма? Мен бұл интерфейсті визуалды түрде екі бөлікке бөлдім. Әдістердің бірінші бөлігі жазу операцияларында қолданылады.

Стандартты жазу операциясы:

  • жаңа нысанның құрылысы және мәселе PostRepository::сақтау
  • PostRepository::getById, нысанды манипуляциялау және шақыру PostRepository::сақтау
  • шақыру PostRepository::dele

Жазу операциялары алу әдістерін қолданбайды. Оқу әрекеттерінде тек get* әдістері қолданылады. Егер сіз туралы оқысаңыз Интерфейсті бөлу принципі (хат I в СОЛИД), сонда біздің интерфейс тым үлкен және кем дегенде екі түрлі жауапкершілікті орындайтыны белгілі болады. Оны екіге бөлудің уақыты келді. Әдіс getById екеуінде де қажет, бірақ қолданба күрделене түскен сайын оның іске асырылуы әр түрлі болады. Мұны сәл кейінірек көреміз. Мен алдыңғы мақалада жазу бөлігінің пайдасыздығы туралы жазғанмын, сондықтан бұл мақалада мен бұл туралы ұмытып кетемін.

Оқу бөлімі маған соншалықты пайдасыз болып көрінеді, өйткені Eloquent үшін мұнда бірнеше іске асыру болуы мүмкін. Сыныпқа қандай ат қою керек? мүмкін ReadPostRepository, бірақ үлгіге Репозиторий оның қазірдің өзінде маңыздылығы аз. Сіз жай ғана аласыз PostQueries:

<?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 жай ғана кейбір іске асыруды сұрайды PostsQueries және онымен жұмыс істейді. Провайдерде біз байланыстырдық PostQueries сыныппен EloquentPostQueries және бұл класс контроллерге ауыстырылады.

Біздің қосымшамыз өте танымал болды деп елестетіп көрейік. Минутына мыңдаған пайдаланушылар соңғы жарияланымдар бар бетті ашады. Ең танымал басылымдар да өте жиі оқылады. Деректер базалары мұндай жүктемелерді өте жақсы өңдей алмайды, сондықтан олар стандартты шешімді - кэшті пайдаланады. Дерекқордан басқа, белгілі бір деректердің суреті белгілі бір операциялар үшін оңтайландырылған жадта сақталады - memcached немесе redis.

Кэштеу логикасы әдетте соншалықты күрделі емес, бірақ оны 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->кэш->есте сақтау бұл жазбаның кэште бар-жоғын тексереді, ал егер жоқ болса, кері қоңырау шалып, қайтарылған мәнді кэшке жазады. Бұл классты қолданбаға енгізу ғана қалады. Интерфейсті іске асыруды сұрау үшін бізге қолданбадағы барлық сыныптар қажет PostQueries класс данасын ала бастады 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) интерфейс деңгейінде оқу және жазу операцияларын толығымен бөлуді ұсынады. Мен оған арқылы келдім Интерфейсті бөлу принципі, бұл менің үлгілер мен принциптерді шебер қолданып, бірінен бірін теорема ретінде шығаруды ұсынады :) Әрине, әрбір жобаға нысандарды таңдау үшін мұндай абстракция қажет емес, бірақ мен сіздермен трюкпен бөлісемін. Қолданудың бастапқы кезеңінде. дамыту үшін сіз жай ғана класс жасай аласыз PostQueries Eloquent арқылы әдеттегі іске асырумен:

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

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

Кэштеу қажеттілігі туындаған кезде, қарапайым жылжыту арқылы осы сыныптың орнына интерфейсті (немесе дерексіз классты) жасауға болады. PostQueries, оның орындалуын сыныпқа көшіріңіз EloquentPostQueries және мен бұрын сипаттаған схемаға өтіңіз. Қолданба кодының қалған бөлігін өзгерту қажет емес.

Барлық осы трюктар сыныптармен, интерфейстермен, Тәуелділікке қарсы инъекция и CQRS бөлімінде егжей-тегжейлі сипатталған менің кітабым «Күрделі веб-қосымшалардың архитектурасы». Осы мақаладағы мысалдардағы менің барлық сабақтарым қорытынды деп белгіленген жұмбақтың шешімі де бар.

Ақпарат көзі: www.habr.com

пікір қалдыру