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 scopes аркылуу ишке ашырууга болот, бирок объект класстарын өзүнө алуу жоопкерчилиги менен ашыкча жүктөө эң жакшы идея эмес жана бул жоопкерчиликти репозиторий класстарга жылдыруу логикалык көрүнөт. ушундайбы? Мен бул интерфейсти атайын эки бөлүккө бөлдүм. Методдордун биринчи бөлүгү жазуу операцияларында колдонулат.

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

  • жаны объектти куруу жана чакырык PostRepository::сактоо
  • PostRepository::getById, объектти манипуляциялоо жана чакыруу PostRepository::сактоо
  • чакыруу PostRepository::delete

Жазуу операциялары алуу ыкмаларын колдонбойт. Окуу операцияларында get* методдору гана колдонулат. жөнүндө окусаңыз Interface Segregation Principe (кат 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();
    }
}

Interface ишке ашыруу менен байланыштуу болушу керек, мисалы 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);
    }
}

Менин бардык каалоолорум провайдерде табигый түрдө сүрөттөлгөн. Ошентип, биз бир классты жазуу жана контейнер конфигурациясын өзгөртүү менен гана биздин суроо-талаптарыбызды кэшөөнү ишке ашырдык. Колдонмонун калган коду өзгөргөн жок.

Албетте, кэшти толугу менен ишке ашыруу үчүн, ошондой эле өчүрүлгөн макала сайтта бир нече убакытка илинбей, дароо жок кылынышы үчүн жараксыздыгын ишке ашыруу керек. Бирок булар майда нерселер.

Жыйынтык: биз бир эмес, эки шаблон колдондук. Үлгү Command Query жоопкерчилигин бөлүү (CQRS) интерфейс деңгээлинде окуу жана жазуу операцияларын толугу менен бөлүүнү сунуштайт. Мен ага аркылуу келдим Interface Segregation Principe, бул мен үлгүлөрдү жана принциптерди чеберчилик менен башкарып, бирин теорема катары алып чыгууну сунуштайт :) Албетте, объекттерди тандоо үчүн мындай абстракция ар бир долбоорго керек эмес, бирок мен сиз менен трюк менен бөлүшөм. иштеп чыгуу, сиз жөн гана класс түзө аласыз PostQueries Eloquent аркылуу кадимки ишке ашыруу менен:

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

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

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

Бардык бул трюктар класстар, интерфейстер, Көз карандылыкты саюу и CQRS майда-чүйдөсүнө чейин баяндалган менин китебим "Татаал веб-тиркемелердин архитектурасы". Бул макаладагы мисалдардагы менин бардык класстарым акыркы деп белгиленген табышмактын да чечими бар.

Source: www.habr.com

Комментарий кошуу