Repositori utili cù Eloquent?

A settimana passata aghju scrittu articulu nantu à l'inutilità di u mudellu Repository per entità Eloquent, in ogni modu, hà prumessu di dì à mè cumu l'utilizanu in parte à u so vantaghju. Per fà questu, pruvaraghju à analizà cumu questu mudellu hè generalmente utilizatu in i prughjetti. L'inseme minimu necessariu di metudi per un repository:

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

In ogni casu, in i prughjetti veri, s'ellu hè statu decisu di utilizà repository, i metudi per ricuperà i registri sò spessu aghjuntu à elli:

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

Questi metudi puderanu esse implementati attraversu scopi Eloquent, ma a sovraccarica di e classi di entità cù a rispunsabilità di ritruvà si ùn hè micca a megliu idea, è trasfurmà sta rispunsabilità à e classi di repository pare logicu. Hè cusì ? I specificamente divisu visualmente sta interfaccia in dui parti. A prima parte di i metudi serà utilizatu in operazioni di scrittura.

L'operazione di scrittura standard hè:

  • custruzzione di un novu ughjettu è sfida PostRepository::save
  • PostRepository::getById, a manipulazione di l'entità è a convocazione PostRepository::save
  • a sfida PostRepository::delete

L'operazioni di scrittura ùn usanu micca metudi di fetch. In l'operazioni di lettura, solu i metudi get* sò usati. Sè vo leghje circa Principiu di Segregazione di l'interfaccia (lettera I в SOLIDU), allora diventerà chjaru chì a nostra interfaccia hè troppu grande è eseguisce almenu duie rispunsabilità diverse. Hè ora di sparte per dui. Metudu getById hè necessariu in i dui, ma cum'è l'applicazione diventa più cumplessa, e so implementazioni seranu diffirenti. Videremu questu un pocu dopu. Aghju scrittu annantu à l'inutilità di a parte di scrittura in un articulu precedente, cusì in questu solu mi ne scurdaraghju.

A parte Read mi pare micca cusì inutile, postu chì ancu per Eloquent pò esse parechje implementazioni quì. Chì nome à a classe? Can ReadPostRepository, ma à u mudellu Repository hà digià pocu pertinenza. Pudete solu PostQueries:

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

Implementà cù Eloquent hè abbastanza simplice:

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

L'interfaccia deve esse assuciata cù l'implementazione, per esempiu in AppServiceProvider:

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

Sta classa hè digià utile. Realizeghja a so rispunsabilità per allevà sia i cuntrolli o a classe di l'entità. In un controller pò esse usatu cusì:

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

Metu PostsController::lastPosts solu dumandà una certa implementazione PostQueries è travaglia cun ellu. In u fornitore avemu ligatu PostQueries cun classe EloquentPostQueries è sta classa serà sustituita in u controller.

Imaginemu chì a nostra applicazione hè diventata assai populari. Migliaia di utilizatori per minutu apre a pagina cù l'ultime publicazioni. I publicazioni più populari sò ancu leghje assai spessu. E basa di dati ùn trattanu micca tali carichi assai bè, perchè usanu una suluzione standard - una cache. In più di a basa di dati, una certa snapshot di dati hè almacenata in almacenamiento ottimizzatu per certe operazioni - memcached o dì di novu.

A logica di cache ùn hè micca cusì complicata, ma l'implementazione in EloquentPostQueries ùn hè micca assai curretta (se solu perchè Principiu di Rispunsabilità Unica). Hè assai più naturali di utilizà un mudellu Decoratore è implementà a caching cum'è decorazione per l'azzione principale:

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

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

Ignorate l'interfaccia Repository in u custruttore. Per una ragione scunnisciuta, anu decisu di nome l'interfaccia per caching in Laravel in questu modu.

Class CachedPostQueries implementa solu a cache. $this->cache->ricordate verifica s'ellu sta entrata hè in a cache è s'ellu ùn hè micca, poi chjama a callback è scrive u valore restituitu à a cache. Tuttu ciò chì resta hè di implementà sta classa in l'applicazione. Avemu bisognu di tutte e classi chì in l'applicazione per dumandà una implementazione di l'interfaccia PostQueries cuminciò à riceve un esempiu di a classa CachedPostQueries. Tuttavia, ellu stessu CachedPostQueries u custruttore deve riceve una classa cum'è paràmetru EloquentPostQueriespostu chì ùn pò micca travaglià senza una implementazione "reale". Cambiamu 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);
    }
}

Tutti i mo desideri sò descritti in modu abbastanza naturale in u fornitore. Cusì, avemu implementatu a cache per e nostre richieste solu scrivendu una classa è cambiendu a cunfigurazione di u containeru. U restu di u codice di l'applicazione ùn hè micca cambiatu.

Di sicuru, per implementà cumplettamente u caching, hè ancu necessariu di implementà l'invalidazione per chì l'articulu sguassatu ùn si ferma micca in u situ per qualchì tempu, ma hè sguassatu immediatamente. Ma queste sò cose minori.

Bottom line: avemu usatu micca unu, ma dui mudelli. Campione Segregazione di Responsabilità di Query Command (CQRS) prupone di separà cumplettamente e operazioni di lettura è scrittura à u livellu di l'interfaccia. Sò ghjuntu à ellu attraversu Principiu di Segregazione di l'interfaccia, chì suggerisce chì aghju manipulatu sapientemente mudelli è principii è derivanu unu da l'altru cum'è un teorema :) Di sicuru, micca ogni prughjettu hà bisognu di una tale astrazione per selezziunà entità, ma sparteraghju u truccu cun voi. À a fase iniziale di l'applicazione sviluppu, pudete simpricimenti creà una classa PostQueries cù l'implementazione abituale via Eloquent:

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

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

Quandu u bisognu di caching si presenta, cù una mossa simplice pudete creà una interfaccia (o classa astratta) in u locu di sta classa. PostQueries, copià a so implementazione à a classe EloquentPostQueries è andate à u schema ch'e aghju descrittu prima. U restu di u codice di l'applicazione ùn deve esse cambiatu.

Tutti questi trucchi cù classi, interfacce, Iniezione di Dipendenza и CQRS descrittu in dettaglio in u mo libru "Architecture of Complex Web Applications". Ci hè ancu una suluzione à l'enigma perchè tutte e mo classi in l'esempii per questu articulu sò marcati cum'è finali.

Source: www.habr.com

Add a comment