Arhive utile cu Eloquent?

Săptămâna trecută am scris articol despre inutilitatea șablonului Repository pentru entitățile Elocvente, cu toate acestea, el a promis că îmi va spune cum să-l folosesc parțial în avantajul său. Pentru a face acest lucru, voi încerca să analizez modul în care acest șablon este utilizat de obicei în proiecte. Setul minim necesar de metode pentru un depozit:

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

Cu toate acestea, în proiectele reale, dacă s-a decis să se utilizeze depozite, la acestea se adaugă adesea metode de recuperare a înregistrărilor:

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

Aceste metode ar putea fi implementate prin domenii Eloquent, dar supraîncărcarea claselor de entități cu responsabilitatea de a se prelua singure nu este cea mai bună idee, iar mutarea acestei responsabilități la clasele de depozit pare logică. E chiar asa? Am împărțit vizual această interfață în două părți. Prima parte a metodelor va fi folosită în operațiile de scriere.

Operația standard de scriere este:

  • construirea unui nou obiect și provocare PostRepository::salvare
  • PostRepository::getById, manipularea și convocarea entităților PostRepository::salvare
  • provocarea PostRepository::delete

Operațiile de scriere nu folosesc metode de preluare. În operațiunile de citire, sunt folosite doar metodele get*. Daca citesti despre Principiul segregării interfeței (scrisoare I в SOLID), atunci va deveni clar că interfața noastră este prea mare și îndeplinește cel puțin două responsabilități diferite. Este timpul să o împărțim la două. Metodă getById este necesar în ambele, dar pe măsură ce aplicația devine mai complexă, implementările sale vor diferi. Vom vedea asta puțin mai târziu. Am scris despre inutilitatea părții de scriere într-un articol anterior, așa că în acesta pur și simplu voi uita de el.

Partea Citire mi se pare că nu este atât de inutilă, deoarece chiar și pentru Eloquent pot exista mai multe implementări aici. Cum să numim clasa? Poate sa ReadPostRepository, dar la șablon Repertoriu el are deja puțină relevanță. Poți doar PostQueries:

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

Implementarea lui cu Eloquent este destul de simplă:

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

Interfața trebuie să fie asociată cu implementarea, de exemplu în AppServiceProvider:

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

Această clasă este deja utilă. El își dă seama de responsabilitatea eliberând fie controlorii, fie clasa de entități. Într-un controler poate fi folosit astfel:

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

metodă PostsController::lastPosts cer doar ceva implementare PostsQueries și lucrează cu el. În furnizorul pe care l-am conectat PostQueries cu clasa EloquentPostQueries iar această clasă va fi înlocuită în controler.

Să ne imaginăm că aplicația noastră a devenit foarte populară. Mii de utilizatori pe minut deschid pagina cu cele mai recente publicații. Cele mai populare publicații sunt, de asemenea, citite foarte des. Bazele de date nu gestionează prea bine astfel de încărcări, așa că folosesc o soluție standard - un cache. Pe lângă baza de date, un anumit instantaneu de date este stocat în stocare optimizată pentru anumite operațiuni - memcached sau Redis.

Logica de stocare în cache nu este de obicei atât de complicată, dar implementarea ei în EloquentPostQueries nu este foarte corectă (fie doar pentru că Principiul de responsabilitate unică). Este mult mai natural să folosești un șablon Decorator și implementați memorarea în cache ca decor pentru acțiunea principală:

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

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

Ignorați interfața Repertoriu în constructor. Dintr-un motiv necunoscut, au decis să numească interfața pentru stocarea în cache în Laravel în acest fel.

clasă CachedPostQueries implementează doar memorarea în cache. $this->cache->remember verifică dacă această intrare este în cache și dacă nu, apoi apelează înapoi și scrie valoarea returnată în cache. Tot ce rămâne este să implementezi această clasă în aplicație. Avem nevoie de toate clasele care în aplicație pentru a solicita o implementare a interfeței PostQueries a început să primească o instanță a clasei CachedPostQueries. Cu toate acestea, el însuși CachedPostQueries constructorul trebuie să primească o clasă ca parametru EloquentPostQueriesdeoarece nu poate funcționa fără o implementare „reală”. Noi schimbăm 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);
    }
}

Toate dorințele mele sunt descrise în mod destul de firesc în furnizor. Astfel, am implementat memorarea în cache pentru cererile noastre doar scriind o clasă și modificând configurația containerului. Restul codului aplicației nu s-a schimbat.

Desigur, pentru a implementa pe deplin stocarea în cache, este, de asemenea, necesar să implementați invalidarea, astfel încât articolul șters să nu se blocheze pe site de ceva timp, ci să fie șters imediat. Dar acestea sunt lucruri minore.

Concluzia: am folosit nu unul, ci două șabloane. Probă Segregarea responsabilităților de interogare de comandă (CQRS) propune separarea completă a operațiunilor de citire și scriere la nivel de interfață. Am venit la el prin intermediul Principiul segregării interfeței, ceea ce sugerează că manipulez cu pricepere tiparele și principiile și deduc unul din celălalt ca o teoremă :) Desigur, nu orice proiect are nevoie de o astfel de abstractizare pentru selectarea entităților, dar voi împărtăși trucul cu voi. La etapa inițială de aplicare dezvoltare, puteți crea pur și simplu o clasă PostQueries cu implementarea obișnuită prin Eloquent:

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

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

Când apare nevoia de stocare în cache, cu o simplă mișcare puteți crea o interfață (sau o clasă abstractă) în locul acestei clase PostQueries, copiați implementarea acesteia în clasă EloquentPostQueries și mergeți la schema pe care am descris-o mai devreme. Restul codului aplicației nu trebuie schimbat.

Toate aceste trucuri cu clase, interfețe, Injecție de dependență и CQRS descrisă în detaliu în cartea mea „Arhitectura aplicațiilor web complexe”. Există, de asemenea, o soluție la ghicitoarea de ce toate clasele mele din exemplele pentru acest articol sunt marcate ca finale.

Sursa: www.habr.com

Adauga un comentariu