Nuttige repositories mei Eloquent?

Ferline wike skreau ik artikel oer de nutteloosheid fan it Repository-sjabloan foar Eloquent entiteiten, lykwols, hy tasein my te fertellen hoe't te brûken it foar in part yn syn foardiel. Om dit te dwaan, sil ik besykje te analysearjen hoe't dit sjabloan normaal wurdt brûkt yn projekten. De minimale fereaske set metoaden foar in repository:

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

Yn echte projekten, as it waard besletten om repositories te brûken, wurde metoaden foar it opheljen fan records faak oan har tafoege:

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

Dizze metoaden kinne wurde ymplementearre fia Eloquent scopes, mar it oerladen fan entiteitsklassen mei de ferantwurdlikens om harsels te heljen is net it bêste idee, en it ferpleatsen fan dizze ferantwurdlikens nei repositoryklassen liket logysk. Is it sa? Ik spesifyk visueel ferdield dizze ynterface yn twa dielen. It earste diel fan 'e metoaden sil brûkt wurde yn skriuwoperaasjes.

De standert skriuwoperaasje is:

  • bou fan in nij objekt en útdaging PostRepository :: bewarje
  • PostRepository::getById, entiteit manipulaasje en summoning PostRepository :: bewarje
  • in útdaging PostRepository :: wiskje

Skriuwoperaasjes brûke gjin opheljemetoaden. Yn lêsoperaasjes wurde allinich get * metoaden brûkt. As jo ​​lêze oer Interface Segregation Principle (letter I в FÊST), dan sil it dúdlik wurde dat ús ynterface te grut is en op syn minst twa ferskillende ferantwurdlikheden útfiert. It is tiid om it troch twa te dielen. Metoade getById is nedich yn beide, mar as de applikaasje wurdt komplekser, syn ymplemintaasjes sille ferskille. Wy sille dit efkes letter sjen. Ik skreau oer de nutteloosheid fan it skriuwdiel yn in earder artikel, dus yn dit sil ik it gewoan ferjitte.

It diel Lês liket my net sa nutteloos, om't sels foar Eloquent hjir ferskate ymplemintaasjes kinne wêze. Wat te neamen de klasse? Kinne ReadPostRepository, mar nei it sjabloan Repository hy hat al in bytsje relevânsje. Jo kinne gewoan PostQueries:

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

It ymplementearjen mei Eloquent is frij ienfâldich:

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

De ynterface moat wurde assosjearre mei de ymplemintaasje, bygelyks yn AppServiceProvider:

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

Dizze klasse is al brûkber. Hy realisearret syn ferantwurdlikens troch de kontrôlers of de entiteitsklasse te ûntlêsten. Yn in controller kin it sa brûkt wurde:

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

Metoade PostsController :: lêste berjochten gewoan freegje om wat ymplemintaasje PostsQueries en wurket dermei. Yn de provider wy keppele PostQueries mei klasse EloquentPostQueries en dizze klasse sil wurde ferfongen yn de controller.

Litte wy ús foarstelle dat ús applikaasje heul populêr is wurden. Tûzenen brûkers per minút iepenje de side mei de lêste publikaasjes. De populêrste publikaasjes wurde ek hiel faak lêzen. Databanken behannelje sokke loads net goed, sadat se in standert oplossing brûke - in cache. Neist de databank wurdt in beskate snapshot fan gegevens opslein yn opslach optimalisearre foar bepaalde operaasjes - memcached of redis.

Cachinglogika is meastentiids net sa yngewikkeld, mar it ymplementearjen yn EloquentPostQueries is net heul korrekt (al is it mar omdat Principe foar inkele ferantwurdlikens). It is folle natuerliker om in sjabloan te brûken Dekorator en ymplemintearje caching as dekoraasje foar de haadaksje:

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

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

Negearje de ynterface Repository yn de konstruktor. Om ien of oare ûnbekende reden besleaten se de ynterface foar caching yn Laravel op dizze manier te neamen.

Klasse CachedPostQueries ymplementearret allinich caching. $this->cache->remember kontrolearret oft dizze yngong yn 'e cache is en sa net, dan ropt werom en skriuwt de weromjûne wearde nei it cache. Alles wat oerbliuwt is dizze klasse yn 'e applikaasje te ymplementearjen. Wy moatte alle klassen dy't yn 'e applikaasje te freegjen in ymplemintaasje fan de ynterface PostQueries begûn te ûntfangen in eksimplaar fan de klasse CachedPostQueries. Lykwols, hy sels CachedPostQueries de constructor moat ûntfange in klasse as parameter EloquentPostQueriessûnt it kin net wurkje sûnder in "echte" ymplemintaasje. Wy feroarje 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);
    }
}

Al myn winsken binne frij natuerlik beskreaun yn 'e provider. Sa hawwe wy caching ymplementearre foar ús oanfragen allinich troch ien klasse te skriuwen en de kontenerkonfiguraasje te feroarjen. De rest fan 'e applikaasjekoade is net feroare.

Fansels, om it caching folslein út te fieren, is it ek nedich om ûnjildigens te ymplementearjen sadat it wiske artikel net in skoft op 'e side hinget, mar fuortdaliks wiske wurdt. Mar dit binne lytse dingen.

Bottom line: wy brûkten net ien, mar twa sjabloanen. Foarbyld Kommando Query Responsibility Segregation (CQRS) stelt foar om lês- en skriuwoperaasjes folslein te skieden op it ynterfacenivo. Ik kaam by him troch Interface Segregation Principle, wat suggerearret dat ik patroanen en prinsipes mei feardigens manipulearje en ien fan 'e oare ôfliede as in stelling :) Fansels hat net elk projekt sa'n abstraksje nedich foar it selektearjen fan entiteiten, mar ik sil de trúk mei jo diele. By it begjin fan tapassing ûntwikkeling, kinne jo gewoan meitsje in klasse PostQueries mei de gewoane ymplemintaasje fia Eloquent:

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

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

As de needsaak foar caching ûntstiet, kinne jo mei in ienfâldige beweging in ynterface (as abstrakte klasse) oanmeitsje yn plak fan dizze klasse PostQueries, kopiearje de ymplemintaasje nei de klasse EloquentPostQueries en gean nei it skema dat ik earder beskreaun. De rest fan 'e applikaasjekoade hoecht net te wizige.

Al dizze trúkjes mei klassen, ynterfaces, Ofhinklikens ynjeksje и CQRS beskreaun yn detail yn myn boek "Arsjitektuer fan komplekse webapplikaasjes". D'r is ek in oplossing foar it riedsel wêrom al myn klassen yn 'e foarbylden foar dit artikel binne markearre as definityf.

Boarne: www.habr.com

Add a comment