Utilaj deponejoj kun Eloquent?

Lastan semajnon mi skribis artikolo pri la senutileco de la ŝablono Deponejo por Elokventaj entoj, tamen, li promesis diri al mi kiel parte uzi ĝin je sia avantaĝo. Por fari tion, mi provos analizi kiel ĉi tiu ŝablono estas kutime uzata en projektoj. La minimuma postulata aro de metodoj por deponejo:

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

Tamen, en realaj projektoj, se estis decidite uzi deponejojn, metodoj por retrovi rekordojn ofte aldoniĝas al ili:

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

Ĉi tiuj metodoj povus esti efektivigitaj per Elokventaj medioj, sed troŝarĝi entajn klasojn kun la respondeco alporti sin ne estas la plej bona ideo, kaj movi ĉi tiun respondecon al deponaj klasoj ŝajnas logika. Ĉu estas tiel? Mi specife vide dividis ĉi tiun interfacon en du partojn. La unua parto de la metodoj estos uzata en skribaj operacioj.

La norma skriboperacio estas:

  • konstruado de nova objekto kaj defio PostDeponejo::konservi
  • PostDeponejo::getById, enta manipulado kaj alvoko PostDeponejo::konservi
  • defio PostRepository::delete

Skribaj operacioj ne uzas fek metodojn. En legaj operacioj, nur get*-metodoj estas uzataj. Se vi legas pri Interfaca Apartigo-Principo (litero I в SOLID), tiam evidentiĝos, ke nia interfaco estas tro granda kaj plenumas almenaŭ du malsamajn respondecojn. Estas tempo dividi ĝin per du. Metodo getById estas necesa en ambaŭ, sed ĉar la aplikaĵo iĝas pli kompleksa, ĝiaj efektivigoj malsamos. Ni vidos ĉi tion iom poste. Pri la senutileco de la skribparto mi skribis en antaŭa artikolo, do en ĉi tiu mi simple forgesos pri ĝi.

La Legu-parto ŝajnas al mi ne tiom senutila, ĉar eĉ por Elokvento povas esti pluraj efektivigoj ĉi tie. Kion nomi la klason? Povas ReadPostRepository, sed al la ŝablono Repository li jam havas malmulte da graveco. Vi povas simple PostDemandoj:

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

Efektivigi ĝin per Eloquent estas sufiĉe simpla:

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

La interfaco devas esti asociita kun la efektivigo, ekzemple en AppServiceProvider:

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

Ĉi tiu klaso jam estas utila. Li realigas sian respondecon malŝarĝante aŭ la regilojn aŭ la unuoklason. En regilo ĝi povas esti uzata jene:

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

Metodo PostsController::lastPosts nur petante iun efektivigon AfiŝojDemandoj kaj laboras kun ĝi. En la provizanto ni ligis PostDemandoj kun klaso ElokventPostQueries kaj ĉi tiu klaso estos anstataŭigita en la regilon.

Ni imagu, ke nia aplikaĵo fariĝis tre populara. Miloj da uzantoj je minuto malfermas la paĝon kun la plej novaj publikaĵoj. La plej popularaj publikaĵoj ankaŭ estas legitaj tre ofte. Datumbazoj ne tre bone traktas tiajn ŝarĝojn, do ili uzas norman solvon - kaŝmemoron. Krom la datumbazo, certa datuma momentfoto estas konservita en stokado optimumigita por certaj operacioj - memcachedripeto.

Kaŝmemora logiko kutime ne estas tiom komplika, sed efektivigi ĝin en EloquentPostQueries ne estas tre ĝusta (se nur ĉar Ununura Respondeca Principo). Estas multe pli nature uzi ŝablonon Dekoraciisto kaj efektivigi kaŝmemoron kiel dekoracion por la ĉefa ago:

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

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

Ignoru la interfacon Repository en la konstrukciisto. Pro iu nekonata kialo, ili decidis nomi la interfacon por kaŝmemoro en Laravel tiamaniere.

Класс CachedPostQueries efektivigas nur kaŝmemoron. $this->cache->memoru kontrolas ĉu ĉi tiu eniro estas en la kaŝmemoro kaj se ne, tiam vokas revokadon kaj skribas la redonitan valoron al la kaŝmemoro. Restas nur efektivigi ĉi tiun klason en la aplikaĵon. Ni bezonas ĉiujn klasojn kiuj en la aplikaĵo por peti efektivigon de la interfaco PostDemandoj komencis ricevi ekzemplon de la klaso CachedPostQueries. Tamen li mem CachedPostQueries la konstrukciisto devas ricevi klason kiel parametron ElokventPostQueriesĉar ĝi ne povas funkcii sen "reala" efektivigo. Ni ŝanĝas 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);
    }
}

Ĉiuj miaj deziroj estas tute nature priskribitaj en la provizanto. Tiel, ni efektivigis kaŝmemoron por niaj petoj nur skribante unu klason kaj ŝanĝante la ujan agordon. La resto de la aplika kodo ne ŝanĝiĝis.

Kompreneble, por plene efektivigi kaŝmemoron, ankaŭ necesas efektivigi malvalidigon, por ke la forigita artikolo ne pendas sur la retejo dum iom da tempo, sed tuj estas forigita. Sed ĉi tiuj estas negravaj aferoj.

Fundo: ni uzis ne unu, sed du ŝablonojn. Specimeno Komando Demanda Respondeco-Apartigo (CQRS) proponas tute apartigi leg- kaj skriboperaciojn ĉe la interfaca nivelo. Mi venis al li tra Interfaca Apartigo-Principo, kio sugestas, ke mi lerte manipulu ŝablonojn kaj principojn kaj derivu unu el la alia kiel teoremo :) Kompreneble, ne ĉiu projekto bezonas tian abstraktadon por elektado de estaĵoj, sed mi dividos la lertaĵon kun vi.En la komenca etapo de aplikado disvolviĝo, vi povas simple krei klason PostDemandoj kun la kutima efektivigo per Eloquent:

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

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

Kiam la bezono por kaŝmemoro ekestas, per simpla movo vi povas krei interfacon (aŭ abstraktan klason) anstataŭ ĉi tiu klaso. PostDemandoj, kopiu ĝian efektivigon al la klaso ElokventPostQueries kaj iru al la skemo, kiun mi priskribis pli frue. La resto de la aplika kodo ne bezonas esti ŝanĝita.

Ĉiuj ĉi tiuj lertaĵoj kun klasoj, interfacoj, Dependa Injekto и CQRS priskribita detale en mia libro "Arkitekturo de Kompleksaj Retaj Aplikoj". Estas ankaŭ solvo al la enigmo, kial ĉiuj miaj klasoj en la ekzemploj por ĉi tiu artikolo estas markitaj kiel finaj.

fonto: www.habr.com

Aldoni komenton