Repositori berguna dengan Eloquent?

Minggu lepas saya menulis artikel tentang ketiadaan templat Repositori untuk entiti Eloquent, walau bagaimanapun, dia berjanji untuk memberitahu saya cara menggunakannya sebahagian untuk kelebihannya. Untuk melakukan ini, saya akan cuba menganalisis cara templat ini biasanya digunakan dalam projek. Set kaedah minimum yang diperlukan untuk repositori:

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

Walau bagaimanapun, dalam projek sebenar, jika diputuskan untuk menggunakan repositori, kaedah untuk mendapatkan semula rekod sering ditambahkan kepada mereka:

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

Kaedah ini boleh dilaksanakan melalui skop Eloquent, tetapi melebihkan kelas entiti dengan tanggungjawab mengambil sendiri bukanlah idea terbaik, dan mengalihkan tanggungjawab ini ke kelas repositori nampaknya logik. Adakah begitu? Saya secara visual membahagikan antara muka ini kepada dua bahagian. Bahagian pertama kaedah akan digunakan dalam operasi tulis.

Operasi tulis standard ialah:

  • pembinaan objek dan cabaran baharu PostRepository::simpan
  • PostRepository::getById, manipulasi entiti dan pemanggilan PostRepository::simpan
  • satu cabaran PostRepository::padam

Operasi tulis tidak menggunakan kaedah ambil. Dalam operasi baca, hanya kaedah get* digunakan. Jika anda membaca tentang Prinsip Pengasingan Antara Muka (surat I Π² PEPEJAL), maka akan menjadi jelas bahawa antara muka kami terlalu besar dan melaksanakan sekurang-kurangnya dua tanggungjawab yang berbeza. Sudah tiba masanya untuk membahagikannya dengan dua. Kaedah getById adalah perlu dalam kedua-duanya, tetapi apabila aplikasi menjadi lebih kompleks, pelaksanaannya akan berbeza. Kita akan lihat ini sedikit kemudian. Saya menulis tentang tidak berguna bahagian tulis dalam artikel sebelumnya, jadi dalam artikel ini saya akan melupakannya.

Bahagian Baca nampaknya saya tidak begitu berguna, kerana walaupun untuk Eloquent mungkin terdapat beberapa pelaksanaan di sini. Apa yang hendak diberi nama kelas? boleh ReadPostRepository, tetapi kepada templat Repositori dia sudah mempunyai sedikit kaitan. Anda boleh sahaja PostQueries:

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

Melaksanakannya dengan Eloquent agak mudah:

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

Antara muka mesti dikaitkan dengan pelaksanaan, contohnya dalam AppServiceProvider:

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

Kelas ini sudah berguna. Dia menyedari tanggungjawabnya dengan melepaskan sama ada pengawal atau kelas entiti. Dalam pengawal ia boleh digunakan seperti ini:

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

Kaedah PostsController::lastPosts hanya meminta beberapa pelaksanaan PostsQueries dan bekerja dengannya. Dalam pembekal yang kami pautkan PostQueries dengan kelas EloquentPostQueries dan kelas ini akan digantikan ke dalam pengawal.

Mari bayangkan bahawa aplikasi kami telah menjadi sangat popular. Beribu-ribu pengguna seminit membuka halaman dengan penerbitan terkini. Penerbitan yang paling popular juga dibaca dengan kerap. Pangkalan data tidak mengendalikan beban sedemikian dengan baik, jadi mereka menggunakan penyelesaian standard - cache. Sebagai tambahan kepada pangkalan data, petikan data tertentu disimpan dalam storan yang dioptimumkan untuk operasi tertentu - memcached atau ulangan.

Logik caching biasanya tidak begitu rumit, tetapi melaksanakannya dalam EloquentPostQueries tidak begitu betul (jika hanya kerana Prinsip Tanggungjawab Tunggal). Ia adalah lebih semula jadi untuk menggunakan templat Penghias dan melaksanakan 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 antara muka Repositori dalam pembina. Atas sebab yang tidak diketahui, mereka memutuskan untuk menamakan antara muka untuk caching dalam Laravel dengan cara ini.

Kelas CachedPostQueries melaksanakan caching sahaja. $this->cache->ingat menyemak sama ada entri ini berada dalam cache dan jika tidak, kemudian memanggil panggilan balik dan menulis nilai yang dikembalikan ke cache. Yang tinggal hanyalah melaksanakan kelas ini ke dalam aplikasi. Kami memerlukan semua kelas yang dalam aplikasi untuk meminta pelaksanaan antara muka PostQueries mula menerima contoh kelas CachedPostQueries. Namun, dia sendiri CachedPostQueries pembina mesti menerima kelas sebagai parameter EloquentPostQuerieskerana ia tidak boleh berfungsi tanpa pelaksanaan "sebenar". Kita berubah 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);
    }
}

Semua keinginan saya diterangkan secara semulajadi dalam pembekal. Oleh itu, kami melaksanakan caching untuk permintaan kami hanya dengan menulis satu kelas dan menukar konfigurasi kontena. Selebihnya kod aplikasi tidak berubah.

Sudah tentu, untuk melaksanakan caching sepenuhnya, ia juga perlu untuk melaksanakan pembatalan supaya artikel yang dipadam tidak digantung di tapak untuk beberapa lama, tetapi dipadamkan serta-merta. Tetapi ini adalah perkara kecil.

Intinya: kami tidak menggunakan satu, tetapi dua templat. Sampel Pengasingan Tanggungjawab Pertanyaan Perintah (CQRS) bercadang untuk mengasingkan sepenuhnya operasi baca dan tulis pada peringkat antara muka. Saya datang kepadanya melalui Prinsip Pengasingan Antara Muka, yang menunjukkan bahawa saya mahir memanipulasi corak dan prinsip dan memperoleh satu daripada yang lain sebagai teorem :) Sudah tentu, tidak setiap projek memerlukan abstraksi sedemikian untuk memilih entiti, tetapi saya akan berkongsi helah dengan anda. Pada peringkat awal aplikasi pembangunan, anda hanya boleh membuat kelas PostQueries dengan pelaksanaan biasa melalui Eloquent:

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

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

Apabila keperluan untuk caching timbul, dengan langkah mudah anda boleh mencipta antara muka (atau kelas abstrak) sebagai ganti kelas ini PostQueries, salin pelaksanaannya ke kelas EloquentPostQueries dan pergi ke skema yang saya terangkan tadi. Selebihnya kod aplikasi tidak perlu ditukar.

Semua helah ini dengan kelas, antara muka, Suntikan Ketergantungan ΠΈ CQRS diterangkan secara terperinci dalam buku saya "Seni Bina Aplikasi Web Kompleks". Terdapat juga penyelesaian kepada teka-teki mengapa semua kelas saya dalam contoh untuk artikel ini ditandakan sebagai muktamad.

Sumber: www.habr.com

Tambah komen