Nëtzlech Repositories mat Eloquent?

Déi lescht Woch hunn ech geschriwwen Artikel iwwer d'Nëtzlosegkeet vun der Repository Schabloun fir Eloquent Entitéiten, Ee, hie versprach mir ze soen, wéi et deelweis zu sengem Virdeel ze benotzen. Fir dëst ze maachen, probéieren ech ze analyséieren wéi dës Schabloun normalerweis a Projete benotzt gëtt. De Minimum erfuerderleche Set vu Methoden fir e Repository:

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

Wéi och ëmmer, a reale Projeten, wann et decidéiert gouf Repositories ze benotzen, ginn d'Methoden fir d'Recrève vun records dacks derbäigesat:

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

Dës Methode kéinten duerch Eloquent Ëmfang ëmgesat ginn, awer d'Iwwerlaaschtung vun Entitéitsklassen mat der Verantwortung fir sech selwer z'erreechen ass net déi bescht Iddi, an dës Verantwortung op Repositoryklassen ze plënneren schéngt logesch. Ass et esou? Ech speziell visuell opgedeelt dëser Interface an zwee Deeler. Den éischten Deel vun de Methoden gëtt a Schreifoperatioune benotzt.

D'Standard Schreifoperatioun ass:

  • Bau vun engem neien Objet an Erausfuerderung PostRepository :: späicheren
  • PostRepository::getById, Entitéit Manipulatioun an Opruff PostRepository :: späicheren
  • der Erausfuerderung PostRepository :: läschen

Schreifoperatioune benotzen keng Erhuelungsmethoden. Bei Liesoperatioune ginn nëmme Get* Methoden benotzt. Wann Dir liesen iwwer Interface Segregatioun Prinzip (Bréif I в SOLID), da gëtt et kloer datt eis Interface ze grouss ass an op d'mannst zwou verschidde Verantwortung ausféiert. Et ass Zäit et duerch zwee ze deelen. Method getById ass noutwendeg a béid, awer wéi d'Applikatioun méi komplex gëtt, wäerte seng Implementatioune ënnerschiddlech sinn. Mir wäerten dat e bësse méi spéit gesinn. Ech hunn iwwer d'Nëtzlosegkeet vum Schreifdeel an engem fréieren Artikel geschriwwen, also an dësem wäert ech et einfach vergiessen.

De Read-Deel schéngt mir net sou nëtzlos ze sinn, well och fir Eloquent kënnen et e puer Implementatiounen hei sinn. Wéi soll d'Klass nennen? Kann ReadPostRepository, awer op d'Schabloun Repository hien huet scho wéineg Relevanz. Dir kënnt just PostQueries:

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

Ëmsetzung et mat Eloquent ass ganz einfach:

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

D'Interface muss mat der Ëmsetzung verbonne sinn, zum Beispill an AppService Provider:

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

Dës Klass ass scho nëtzlech. Hie realiséiert seng Verantwortung andeems hien entweder d'Controller oder d'Entitéitsklass entléisst. An engem Controller kann et esou benotzt ginn:

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

Methode PostsController :: lescht Posts just fir eng Ëmsetzung froen PostsQueries a schafft domat. Am Provider mir verlinkt PostQueries mat Klass EloquentPostQueries an dës Klass gëtt an de Controller ersat.

Loosst eis virstellen datt eis Applikatioun ganz populär ginn ass. Dausende vu Benotzer pro Minutt oppen der Säit mat de leschten Publikatiounen. Déi populärste Publikatioune ginn och ganz dacks gelies. Datenbanken handhaben esou Lasten net ganz gutt, sou datt se eng Standardléisung benotzen - e Cache. Zousätzlech zu der Datebank gëtt e bestëmmten Date-Snapshot an der Späichere gespäichert optimiséiert fir gewësse Operatiounen - memcached oder redis.

Caching Logik ass normalerweis net sou komplizéiert, awer et an EloquentPostQueries ëmzesetzen ass net ganz korrekt (wann nëmmen well Eenzele Verantwortungsprinzip). Et ass vill méi natierlech eng Schabloun ze benotzen Dekorateur an ëmsetzen Caching als Dekoratioun fir d'Haaptaktioun:

<?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éieren den Interface Repository am Konstruktor. Aus e puer onbekannte Grënn hunn se décidéiert d'Interface fir de Caching am Laravel op dës Manéier ze nennen.

Klass CachedPostQueries implementéiert nëmmen Caching. $this->cache->erënneren iwwerpréift ob dës Entrée am Cache ass a wann net, dann rufft den Réckruff un a schreift de zréckgeschéckten Wäert an de Cache. Alles wat bleift ass dës Klass an d'Applikatioun ëmzesetzen. Mir brauchen all Klassen, déi an der Applikatioun fir eng Ëmsetzung vun der Interface ze froen PostQueries ugefaang eng Instanz vun der Klass ze kréien CachedPostQueries. Wéi och ëmmer, hie selwer CachedPostQueries de Konstruktor muss eng Klass als Parameter kréien EloquentPostQuerieswell et ouni eng "richteg" Ëmsetzung net funktionnéiert. Mir änneren AppService Provider:

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

All meng Wënsch sinn ganz natierlech am Provider beschriwwen. Also hu mir Cache fir eis Ufroe implementéiert nëmmen andeems mir eng Klass schreiwen an d'Containerkonfiguratioun änneren. De Rescht vum Applikatiounscode ass net geännert.

Natierlech, fir de Cache komplett ëmzesetzen, ass et och néideg d'Invalidatioun ëmzesetzen, sou datt de geläschte Artikel net fir eng Zäit um Site hänkt, awer direkt geläscht gëtt. Awer dëst si kleng Saachen.

Ënnen Linn: mir hunn net eng, mä zwee Schablounen benotzt. Prouf Command Query Responsibility Segregation (CQRS) proposéiert fir Lies- a Schreifoperatiounen um Interfaceniveau komplett ze trennen. Ech koum him duerch Interface Segregatioun Prinzip, wat suggeréiert datt ech Musteren a Prinzipien kompetent manipuléieren an een aus deem aneren als Theorem ofleeden :) Natierlech brauch net all Projet esou eng Abstraktioun fir d'Entitéiten auszewielen, mee ech wäert den Trick mat Iech deelen.An der éischter Etapp vun der Uwendung Entwécklung, Dir kënnt einfach eng Klass schafen PostQueries mat der üblecher Ëmsetzung iwwer Eloquent:

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

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

Wann de Besoin fir Caching entsteet, kënnt Dir mat enger einfacher Beweegung en Interface (oder abstrakt Klass) amplaz vun dëser Klass erstellen PostQueries, kopéiert seng Ëmsetzung an d'Klass EloquentPostQueries a gitt op de Schema, deen ech virdru beschriwwen hunn. De Rescht vum Applikatiounscode muss net geännert ginn.

All dës Tricken mat Klassen, Interfaces, Ofhängegkeet Injektioun и CQRS am Detail beschriwwen an mäi Buch "Architecture of Complex Web Applications". Et gëtt och eng Léisung fir d'Rätsel firwat all meng Klassen an de Beispiller fir dësen Artikel als final markéiert sinn.

Source: will.com

Setzt e Commentaire