Repositori yang berguna dengan Eloquent?

Minggu lalu saya menulis artikel tentang tidak bergunanya templat Repositori untuk entitas Eloquent, namun, dia berjanji akan memberi tahu saya cara menggunakannya sebagian untuk keuntungannya. Untuk melakukan ini, saya akan mencoba menganalisis bagaimana template ini biasanya digunakan dalam proyek. Kumpulan metode minimum yang diperlukan untuk repositori:

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

Namun, dalam proyek nyata, jika diputuskan untuk menggunakan repositori, metode untuk mengambil catatan sering ditambahkan ke dalamnya:

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

Metode ini dapat diimplementasikan melalui cakupan Eloquent, namun membebani kelas entitas dengan tanggung jawab mengambil dirinya sendiri bukanlah ide terbaik, dan memindahkan tanggung jawab ini ke kelas repositori tampaknya logis. Apakah begitu? Saya secara khusus membagi antarmuka ini menjadi dua bagian. Bagian pertama dari metode ini akan digunakan dalam operasi penulisan.

Operasi penulisan standar adalah:

  • pembangunan objek dan tantangan baru PostRepositori::simpan
  • PostRepositori::getById, manipulasi dan pemanggilan entitas PostRepositori::simpan
  • tantangan PostRepository::hapus

Operasi tulis tidak menggunakan metode pengambilan. Dalam operasi baca, hanya metode get* yang digunakan. Jika Anda membaca tentang Prinsip Pemisahan Antarmuka (surat I Π² PADAT), maka akan menjadi jelas bahwa antarmuka kita terlalu besar dan melakukan setidaknya dua tanggung jawab yang berbeda. Saatnya membaginya menjadi dua. metode dapatkanById keduanya diperlukan, namun ketika aplikasi menjadi lebih kompleks, implementasinya akan berbeda. Kita akan melihatnya nanti. Saya menulis tentang tidak bergunanya bagian menulis di artikel sebelumnya, jadi di artikel ini saya akan melupakannya begitu saja.

Bagian Read menurut saya tidak terlalu berguna, karena bahkan untuk Eloquent mungkin ada beberapa implementasi di sini. Apa nama kelasnya? Bisa BacaPostRepositori, tetapi ke templat Gudang dia sudah memiliki sedikit relevansi. Anda bisa saja Pertanyaan Posting:

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

Menerapkannya dengan Eloquent cukup sederhana:

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

Antarmuka harus dikaitkan dengan implementasi, misalnya di Penyedia Layanan Aplikasi:

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

Kelas ini sudah berguna. Dia menyadari tanggung jawabnya dengan membongkar pengontrol atau kelas entitas. Di pengontrol dapat digunakan seperti ini:

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

metode PostsController::lastPosts hanya meminta beberapa implementasi PostingPertanyaan dan bekerja dengannya. Di penyedia yang kami tautkan Pertanyaan Posting dengan kelas Pertanyaan EloquentPost dan kelas ini akan diganti menjadi pengontrol.

Bayangkan aplikasi kita menjadi sangat populer. Ribuan pengguna per menit membuka halaman dengan publikasi terbaru. Publikasi terpopuler juga sangat sering dibaca. Basis data tidak menangani beban seperti itu dengan baik, jadi mereka menggunakan solusi standar - cache. Selain database, snapshot data tertentu disimpan dalam penyimpanan yang dioptimalkan untuk operasi tertentu - memcached ΠΈΠ»ΠΈ redis.

Logika caching biasanya tidak terlalu rumit, tetapi mengimplementasikannya di EloquentPostQueries tidak terlalu tepat (jika hanya karena Prinsip Tanggung Jawab Tunggal). Jauh lebih alami menggunakan templat Penghias dan menerapkan caching sebagai hiasan untuk tindakan utama:

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

    // Π΄Ρ€ΡƒΠ³ΠΈΠ΅ ΠΌΠ΅Ρ‚ΠΎΠ΄Ρ‹ практичСски Ρ‚Π°ΠΊΠΈΠ΅ ΠΆΠ΅
}

Abaikan antarmuka Gudang di konstruktor. Untuk beberapa alasan yang tidak diketahui, mereka memutuskan untuk memberi nama antarmuka untuk caching di Laravel dengan cara ini.

Kelas Kueri Posting Tembolok hanya mengimplementasikan caching. $ini->cache->ingat memeriksa apakah entri ini ada di cache dan jika tidak, panggil panggilan balik dan tulis nilai yang dikembalikan ke cache. Yang tersisa hanyalah mengimplementasikan kelas ini ke dalam aplikasi. Kita memerlukan semua kelas yang ada dalam aplikasi untuk meminta implementasi antarmuka Pertanyaan Posting mulai menerima instance kelas Kueri Posting Tembolok. Namun, dia sendiri Kueri Posting Tembolok konstruktor harus menerima kelas sebagai parameter Pertanyaan EloquentPostkarena ini tidak dapat berjalan tanpa implementasi yang "nyata". Kita berubah Penyedia Layanan Aplikasi:

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

Semua keinginan saya dijelaskan secara alami di penyedia. Jadi, kami mengimplementasikan caching untuk permintaan kami hanya dengan menulis satu kelas dan mengubah konfigurasi container. Kode aplikasi lainnya tidak berubah.

Tentu saja, untuk mengimplementasikan caching secara penuh, perlu juga dilakukan penerapan invalidasi agar artikel yang dihapus tidak hang di situs untuk beberapa waktu, tetapi segera dihapus. Tapi ini hanyalah hal-hal kecil.

Intinya: kami menggunakan bukan hanya satu, tapi dua templat. Sampel Pemisahan Tanggung Jawab Permintaan Perintah (CQRS) mengusulkan untuk sepenuhnya memisahkan operasi baca dan tulis di tingkat antarmuka. Saya datang kepadanya melalui Prinsip Pemisahan Antarmuka, yang menunjukkan bahwa saya dengan terampil memanipulasi pola dan prinsip dan mengambil satu dari yang lain sebagai sebuah teorema :) Tentu saja, tidak setiap proyek memerlukan abstraksi seperti itu untuk memilih entitas, tetapi saya akan membagikan triknya kepada Anda. pengembangan, Anda cukup membuat kelas Pertanyaan Posting dengan implementasi biasa melalui Eloquent:

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

    // Π΄Ρ€ΡƒΠ³ΠΈΠ΅ ΠΌΠ΅Ρ‚ΠΎΠ΄Ρ‹
}

Ketika kebutuhan akan caching muncul, dengan gerakan sederhana Anda dapat membuat antarmuka (atau kelas abstrak) sebagai pengganti kelas ini Pertanyaan Posting, salin implementasinya ke kelas Pertanyaan EloquentPost dan pergi ke skema yang saya jelaskan sebelumnya. Kode aplikasi lainnya tidak perlu diubah.

Semua trik ini dengan kelas, antarmuka, Injeksi Ketergantungan ΠΈ CQRS dijelaskan secara rinci di buku saya β€œArsitektur Aplikasi Web Kompleks”. Ada juga solusi untuk teka-teki mengapa semua kelas saya dalam contoh artikel ini ditandai sebagai final.

Sumber: www.habr.com

Tambah komentar