Przydatne repozytoria z Eloquent?

W zeszłym tygodniu pisałem artykuł o bezużyteczności szablonu Repozytorium dla encji Eloquentobiecał jednak, że powie mi, jak częściowo wykorzystać to na swoją korzyść. W tym celu spróbuję przeanalizować, w jaki sposób ten szablon jest zwykle wykorzystywany w projektach. Minimalny wymagany zestaw metod dla repozytorium:

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

Jednak w realnych projektach, jeśli zdecydowano się na wykorzystanie repozytoriów, często dodawane są do nich metody wyszukiwania rekordów:

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

Metody te można wdrożyć poprzez zakresy Eloquent, ale przeciążanie klas jednostek odpowiedzialnością za samopobranie nie jest najlepszym pomysłem i przeniesienie tej odpowiedzialności na klasy repozytorium wydaje się logiczne. Czy tak jest? Specjalnie wizualnie podzieliłem ten interfejs na dwie części. Pierwsza część metod będzie wykorzystywana w operacjach zapisu.

Standardowa operacja zapisu to:

  • budowa nowego obiektu i wyzwanie PostRepository::zapisz
  • PostRepository::getById, manipulowanie istotami i przywoływanie PostRepository::zapisz
  • wyzwanie Repozytorium postów::usuń

Operacje zapisu nie korzystają z metod pobierania. W operacjach odczytu używane są tylko metody get*. Jeśli czytasz o Zasada segregacji interfejsów (list I в SOLID), wtedy stanie się jasne, że nasz interfejs jest za duży i pełni co najmniej dwie różne obowiązki. Czas podzielić to przez dwa. metoda getById jest konieczne w obu przypadkach, ale w miarę jak aplikacja staje się bardziej złożona, jej implementacje będą się różnić. Zobaczymy to trochę później. O bezużyteczności części do pisania pisałam już w poprzednim artykule, więc w tym po prostu o tym zapomnę.

Część Read wydaje mi się nie tak bezużyteczna, ponieważ nawet dla Eloquent może być tutaj kilka implementacji. Jak nazwać klasę? Móc Repozytorium ReadPost, ale do szablonu Magazyn on już ma niewielkie znaczenie. Możesz po prostu Zapytania pocztowe:

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

Implementacja go za pomocą Eloquent jest dość prosta:

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

Interfejs musi być powiązany z implementacją np Dostawca usług aplikacji:

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

Ta klasa jest już przydatna. Realizuje swoją odpowiedzialność poprzez odciążenie kontrolerów lub klasy encji. W kontrolerze można go użyć w następujący sposób:

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

metoda Kontroler postów::ostatnie posty po prostu proszę o jakąś implementację PostyZapytania i z nim współpracuje. U dostawcy, którego połączyliśmy Zapytania pocztowe z klasą Zapytania elokwentne i ta klasa zostanie podstawiona w kontrolerze.

Wyobraźmy sobie, że nasza aplikacja stała się bardzo popularna. Tysiące użytkowników na minutę otwiera stronę z najnowszymi publikacjami. Bardzo często czytane są także najpopularniejsze publikacje. Bazy danych słabo radzą sobie z takimi obciążeniami, dlatego korzystają ze standardowego rozwiązania – pamięci podręcznej. Oprócz bazy danych pewna migawka danych jest przechowywana w pamięci zoptymalizowanej pod kątem określonych operacji - memcached lub redis.

Logika buforowania zwykle nie jest aż tak skomplikowana, ale jej implementacja w EloquentPostQueries nie jest zbyt poprawna (choćby dlatego, że Zasada pojedynczej odpowiedzialności). O wiele bardziej naturalne jest użycie szablonu Dekorator i zaimplementuj buforowanie jako dekorację głównej akcji:

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

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

Pomiń interfejs Magazyn w konstruktorze. Z nieznanego powodu postanowiono nazwać w ten sposób interfejs buforowania w Laravel.

Klasa Zapytania dotyczące buforowanych postów implementuje tylko buforowanie. $this->cache->pamiętaj sprawdza, czy ten wpis znajduje się w pamięci podręcznej, a jeśli nie, wywołuje wywołanie zwrotne i zapisuje zwróconą wartość do pamięci podręcznej. Pozostaje tylko zaimplementować tę klasę w aplikacji. Potrzebujemy wszystkich klas znajdujących się w aplikacji, aby zażądać implementacji interfejsu Zapytania pocztowe zaczął otrzymywać instancję klasy Zapytania dotyczące buforowanych postów. Jednak on sam Zapytania dotyczące buforowanych postów konstruktor musi otrzymać klasę jako parametr Zapytania elokwentneponieważ nie może działać bez „prawdziwej” implementacji. Zmieniamy się Dostawca usług aplikacji:

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

Wszystkie moje życzenia są w sposób naturalny opisane u dostawcy. Dlatego zaimplementowaliśmy buforowanie naszych żądań tylko poprzez napisanie jednej klasy i zmianę konfiguracji kontenera. Pozostała część kodu aplikacji nie uległa zmianie.

Oczywiście, aby w pełni wdrożyć buforowanie, konieczne jest również wdrożenie unieważnienia, aby usunięty artykuł nie wisiał na stronie przez jakiś czas, ale został natychmiast usunięty. Ale to są drobne rzeczy.

Konkluzja: użyliśmy nie jednego, ale dwóch szablonów. Próbka Podział odpowiedzialności za zapytania dotyczące poleceń (CQRS) proponuje całkowite oddzielenie operacji odczytu i zapisu na poziomie interfejsu. Dotarłem do niego przez Zasada segregacji interfejsów, co sugeruje, że umiejętnie manipuluję wzorami i zasadami i wyprowadzam jedno z drugiego w formie twierdzenia :) Oczywiście nie każdy projekt potrzebuje takiej abstrakcji przy wyborze bytów, ale podzielę się z Wami trikiem.Na początkowym etapie stosowania development, możesz po prostu stworzyć klasę Zapytania pocztowe ze zwykłą implementacją za pośrednictwem Eloquent:

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

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

Gdy zajdzie potrzeba buforowania, prostym ruchem możesz utworzyć interfejs (lub klasę abstrakcyjną) zamiast tej klasy Zapytania pocztowe, skopiuj jego implementację do klasy Zapytania elokwentne i przejdź do schematu, który opisałem wcześniej. Pozostała część kodu aplikacji nie wymaga zmiany.

Wszystkie te sztuczki z klasami, interfejsami, Wstrzykiwanie zależności и CQRS szczegółowo opisane w moja książka „Architektura złożonych aplikacji internetowych”. Istnieje również rozwiązanie zagadki, dlaczego wszystkie moje zajęcia w przykładach do tego artykułu są oznaczone jako ostateczne.

Źródło: www.habr.com

Dodaj komentarz