Repository të dobishme me Eloquent?

Java javë shkrova një artikull mbi pafunësinë e shabllonit Repository për entitetet Eloquent, por premtova të tregoj se si mund të përdoret pjesërisht me dobi. Për këtë do të përpiqem të analizoj se si zakonisht përdoret ky shabllon në projekte. Grupi minimal i nevojshëm i metodave për regjistrimin:

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

Megjithatë, në projekte reale, nëse është vendosur të përdoren regjistrat, shpesh shtohen metoda për seleksionimin e të dhënave:

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

Këto metoda mund të realizoheshin përmes scopes Eloquent, por mbingarkimi i klasave të entiteteve me detyra për seleksionimin e vetvetes — nuk është një ide shumë e mirë dhe transferimi i kësaj përgjegjësie në klasat e regjistrave duket logjik. A është kështu? E kam ndarë këtë interfejs vizualisht në dy pjesë. Pjesa e parë e metodave do të përdoret në operacione shkrimi.

Operacionet standarde të shkrimit janë:

  • konstruktimi i një objekti të ri dhe thirrja e PostRepository::save
  • PostRepository::getById, manipulimet me entitetin dhe thirrja e PostRepository::save
  • thirrja PostRepository::delete

Në operacionet e shkrimit nuk ka përdorim të metodave të seleksionit. Në operacionet e leximit, megjithatë përdoren vetëm metodat get*. Nëse lexoni mbi Interface Segregation Principle (shkronja ISOLID), atëherë do të kuptoni se interfejsi ynë doli shumë i madh dhe kryen të paktën dy përgjegjësi të ndryshme. Është koha të ndahen në dy. Metoda getById nevojitet në të dyja, megjithatë me ndërlikimin e aplikacionit realizimet e saj do të jenë të ndryshme. Këtë do ta shohim pak më vonë. Për pafunësinë e pjesës shkrimore kam shkruar në artikullin e kaluar, kështu që në këtë unë do ta harroj atë.

Pjesa e leximit më duket e dobishme, pasi edhe për Eloquent mund të ketë disa realizime. Si të quhet klasa? Mund të quhet ReadPostRepository, por për shabllonin Repository atë ka marrë pak lidhje. Mundet thjesht PostQueries:

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

Realizimi i tij me Eloquent është relativisht i thjeshtë:

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

Interfejsi duhet të lidhet me implementimin, për shembull në AppServiceProvider:

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

Ky klasë është tashmë e dobishme. Ai realizon përgjegjësinë e tij, duke lehtësuar kështu ose kontrollorët, ose klasën e entitetit. Në kontrollor mund të përdoret kështu:

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

Sanitizer.replaceElementWithChildren() PostsController::lastPosts thjesht kërkon një implementim për veten e tij PostsQueries dhe punon me të. Në ofrues kemi lidhur PostQueries me klasën EloquentPostQueries dhe në kontrollor ky klasë do të vendoset.

Le të imagjinojmë që aplikacioni ynë është bërë shumë popullor. Mijëra përdorues në minutë hapin faqen me publikimet më të fundit. Publikimet më të njohura gjithashtu lexohen shumë shpesh. Bazat e të dhënave nuk e menaxhojnë mirë një ngarkesë të tillë, prandaj përdoren një zgjidhje standarde - keq. Përveç databazës, një Kopje e të dhënave ruhet në një magazinë të optimizuar për operacione të caktuara - memcached или redis.

Logjika e keq-keshimit zakonisht nuk është aq e komplikuar, por ta implementosh atë në EloquentPostQueries nuk është shumë e drejtë (në çdo rast për shkak të Parimi i Përgjegjësisë së Vetme). Më natyrshëm është të përdorim modelin Dekorator dhe të realizojmë keq-keshimin si dekorim të veprimit kryesor:

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

    // metodat e tjera janë pothuajse të njëjta
}

Mos i kushtoni vëmendje ndërfaqes Repository në ndihmësin. Për një arsye të panjohur, kështu vendosën ta quajnë ndërfaqen për ruajtjen në Laravel.

Klasa CachedPostQueries implementon vetëm ruajtjen. $this->cache->remember kontrollon nëse ky regjistrim është në cache dhe nëse nuk është, thërret callback dhe ruan vlerën e kthyer në cache. Na mbetet vetëm të integrojmë këtë klasë në aplikacion. Na nevojitet që të gjitha klasat që kërkojnë implementimin e ndërfaqes PostQueries të fillojnë të marrin një ekzemplar të klasës CachedPostQueries. Megjithatë, vetë CachedPostQueries si parametrin në konstruktori duhet të marrë klasën EloquentPostQueries, pasi nuk mund të punojë pa një implementim "të vërtetë". Ndryshojmë AppServiceProvider:

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

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

Të gjitha dëshirat e mia përshkruhen mjaft natyrshëm në provajder. Kështu, ne implementuam ruajtjen për kërkesat tona duke shkruar vetëm një klasë dhe duke ndryshuar konfigurimin e enës. Kodi i aplikacionit të mbetur nuk është ndryshuar.

Sigurisht, për një implementim të plotë të ruajtjes është e nevojshme gjithashtu të implementohet invalidimi, në mënyrë që artikulli i fshirë të mos mbetet në faqen e internetit për një kohë të gjatë por të fshihet menjëherë. Por kjo është një gjë e vogël.

Përfundimi: ne përdorëm jo një, por dy shabllone. Shablloni Command Query Responsibility Segregation (CQRS) ofron të ndahet plotësisht operacionet e leximit dhe shkruarjes në nivelin e ndërfaqeve. Unë arrita te ai përmes Interface Segregation Principle, që tregon se unë manovroj me shabllone dhe parime dhe nxjerr një nga tjetërin si një teoremë 🙂 Sigurisht, jo çdo projekt ka nevojë për një abstraksion të tillë për zgjedhjen e entiteteve, por do të ndaj me ju një hile. Në fazën fillestare të zhvillimit të aplikacionit, thjesht mund të krijoni një klasë PostQueries me një implementim të zakonshëm përmes Eloquent:

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

    // metoda të tjera
}

Kur të lindë nevoja për ruajtje, me një lëvizje të lehtë mund të krijoni një ndërfaqe (ose klasë abstrakte) në vend të kësaj klase PostQueries, implementimin e saj ta kopjoni në klasën EloquentPostQueries dhe të kaloni në skemën që e kam përshkruar më parë. Kodi tjetër i aplikacionit nuk duhet të ndryshohet.

Të gjitha këto trukime me klasa, ndërfaqe, Dependency Injection dhe CQRS janë përshkruar në detaje në libri im më tepër "Arkitektura e aplikacioneve komplekse të uebit". Atje është edhe zgjidhja e enigmatike përse të gjitha klasat e mia në shembujt e këtij artikulli janë markuar si final.

Burimi: habr.com

Blini hostim të besueshëm për faqe interneti me mbrojtje DDoS, serverë VPS VDS 🔥 Blini hostim të besueshëm për faqe interneti me mbrojtje DDoS, serverë VPS VDS - ProHoster