Noderīgas krātuves ar Eloquent?

Pagājušajā nedēļā es rakstīju raksts par repozitorija veidnes nederīgumu Eloquent entītijāmtomēr viņš apsolīja man pastāstīt, kā to daļēji izmantot savā labā. Lai to izdarītu, es mēģināšu analizēt, kā šī veidne parasti tiek izmantota projektos. Repozitorija minimālais nepieciešamais metožu kopums:

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

Tomēr reālos projektos, ja tika nolemts izmantot repozitorijus, tiem bieži tiek pievienotas ierakstu izguves metodes:

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

Šīs metodes varētu ieviest, izmantojot Eloquent tvērumus, taču pārslogot entītiju klases, uzņemoties pašas par sevi, nav labākā ideja, un šīs atbildības pārvietošana uz repozitoriju klasēm šķiet loģiska. Vai tā ir? Es īpaši vizuāli sadalīju šo saskarni divās daļās. Pirmā metožu daļa tiks izmantota rakstīšanas operācijās.

Standarta rakstīšanas darbība ir šāda:

  • jauna objekta celtniecība un izaicinājums PostRepository::save
  • PostRepository::getById, entītiju manipulācijas un izsaukšana PostRepository::save
  • izaicinājums PostRepository::delete

Rakstīšanas operācijās netiek izmantotas ielādes metodes. Lasīšanas operācijās tiek izmantotas tikai get* metodes. Ja lasi par Interfeisa segregācijas princips (vēstule I в SOLID), tad kļūs skaidrs, ka mūsu saskarne ir pārāk liela un veic vismaz divus dažādus pienākumus. Ir pienācis laiks to sadalīt ar diviem. Metode getById ir nepieciešama abos, taču, lietojumprogrammai kļūstot sarežģītākai, tās ieviešanas būs atšķirīgas. To mēs redzēsim nedaudz vēlāk. Es rakstīju par rakstīšanas daļas bezjēdzību iepriekšējā rakstā, tāpēc šajā es par to vienkārši aizmirsīšu.

Lasīšanas daļa man nešķiet tik bezjēdzīga, jo pat Eloquent šeit var būt vairākas ieviešanas. Kā nosaukt klasi? Var ReadPostRepository, bet uz veidni Krātuve viņam jau ir maza nozīme. Jūs varat vienkārši PostQuerys:

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

To ieviest ar Eloquent ir pavisam vienkārši:

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

Saskarnei jābūt saistītai ar ieviešanu, piemēram, in AppServiceProvider:

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

Šī klase jau ir noderīga. Viņš apzinās savu atbildību, atbrīvojot vai nu kontrolierus, vai entītiju klasi. Kontrolerī to var izmantot šādi:

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

Metode PostsController::lastPosts tikai prasu kādu ieviešanu PostsQuerys un strādā ar to. Mūsu piesaistītajā pakalpojumu sniedzējā PostQuerys ar klasi EloquentPostQueries un šī klase tiks aizstāta ar kontrolieri.

Iedomāsimies, ka mūsu aplikācija ir kļuvusi ļoti populāra. Tūkstošiem lietotāju minūtē atver lapu ar jaunākajām publikācijām. Ļoti bieži tiek lasītas arī populārākās publikācijas. Datu bāzes ar šādām slodzēm netiek galā īpaši labi, tāpēc izmanto standarta risinājumu – kešatmiņu. Papildus datu bāzei noteikts datu momentuzņēmums tiek glabāts krātuvē, kas ir optimizēts noteiktām darbībām - memcached vai Redis.

Kešatmiņas loģika parasti nav tik sarežģīta, taču tās ieviešana programmā EloquentPostQueries nav pārāk pareiza (ja tikai tāpēc, ka Vienotas atbildības princips). Daudz dabiskāk ir izmantot veidni Dekorators un ieviest kešatmiņu kā galvenās darbības noformējumu:

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

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

Ignorējiet saskarni Krātuve konstruktorā. Nezināma iemesla dēļ viņi nolēma šādi nosaukt Laravel kešatmiņas interfeisu.

Klase CachedPostQueries ievieš tikai kešatmiņu. $this->cache->atcerēties pārbauda, ​​vai šis ieraksts ir kešatmiņā un ja nav, tad izsauc atzvanīšanu un ieraksta atgriezto vērtību kešatmiņā. Atliek tikai ieviest šo klasi lietojumprogrammā. Mums ir vajadzīgas visas lietojumprogrammas klases, lai pieprasītu saskarnes ieviešanu PostQuerys sāka saņemt klases piemēru CachedPostQueries. Tomēr viņš pats CachedPostQueries konstruktoram kā parametrs jāsaņem klase EloquentPostQueriesjo tas nevar darboties bez "īstas" ieviešanas. Mēs maināmies 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);
    }
}

Visas manas vēlmes ir diezgan dabiski aprakstītas pakalpojumu sniedzējā. Tādējādi mēs ieviesām kešatmiņu saviem pieprasījumiem, tikai rakstot vienu klasi un mainot konteinera konfigurāciju. Pārējais lietojumprogrammas kods nav mainījies.

Protams, lai pilnībā ieviestu kešatmiņu, ir jāievieš arī nederība, lai dzēstais raksts kādu laiku nekarātos vietnē, bet tiktu nekavējoties izdzēsts. Bet tās ir mazsvarīgas lietas.

Secinājums: mēs izmantojām nevis vienu, bet divas veidnes. Paraugs Command Query Responsibility Segregation (CQRS) ierosina pilnībā nodalīt lasīšanas un rakstīšanas darbības saskarnes līmenī. Es nonācu pie viņa caur Interfeisa segregācijas princips, kas liek domāt, ka es prasmīgi manipulēju ar modeļiem un principiem un atvasinu vienu no otra kā teorēmu :) Protams, ne katram projektam ir vajadzīga šāda entītiju atlases abstrakcija, bet es padalīšos ar jums. attīstību, varat vienkārši izveidot klasi PostQuerys ar parasto ieviešanu, izmantojot Eloquent:

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

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

Kad rodas vajadzība pēc kešatmiņas, ar vienkāršu pārvietošanu varat izveidot interfeisu (vai abstraktu klasi) šīs klases vietā PostQuerys, kopējiet tā ieviešanu klasē EloquentPostQueries un dodieties uz iepriekš aprakstīto shēmu. Pārējais lietojumprogrammas kods nav jāmaina.

Visi šie triki ar klasēm, saskarnēm, Atkarības injekcija и CQRS detalizēti aprakstīts mana grāmata “Sarežģītu tīmekļa lietojumprogrammu arhitektūra”. Ir arī risinājums mīklai, kāpēc visas manas nodarbības šī raksta piemēros ir atzīmētas kā galīgas.

Avots: www.habr.com

Pievieno komentāru