Kho lưu trữ hữu ích với Eloquent?

Tuần trước tôi đã viết bài viết về sự vô dụng của mẫu Kho lưu trữ đối với các thực thể Eloquenttuy nhiên, anh ấy hứa sẽ cho tôi biết cách sử dụng nó một phần để có lợi cho anh ấy. Để làm điều này, tôi sẽ cố gắng phân tích cách mẫu này thường được sử dụng trong các dự án. Bộ phương thức bắt buộc tối thiểu cho một kho lưu trữ:

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

Tuy nhiên, trong các dự án thực tế, nếu quyết định sử dụng kho lưu trữ, các phương thức truy xuất bản ghi thường được thêm vào chúng:

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

Các phương thức này có thể được triển khai thông qua phạm vi Eloquent, nhưng việc nạp chồng các lớp thực thể với trách nhiệm tự tìm nạp không phải là ý tưởng hay nhất và việc chuyển trách nhiệm này sang các lớp kho lưu trữ có vẻ hợp lý. Có phải vậy không? Tôi đặc biệt chia giao diện này thành hai phần một cách trực quan. Phần đầu tiên của các phương thức sẽ được sử dụng trong các thao tác ghi.

Hoạt động ghi tiêu chuẩn là:

  • xây dựng một đối tượng mới và thách thức Kho lưu trữ bài viết::lưu
  • Kho lưu trữ bài viết::getById, thao túng và triệu hồi thực thể Kho lưu trữ bài viết::lưu
  • thử thách PostRepository::xóa

Hoạt động ghi không sử dụng phương thức tìm nạp. Trong hoạt động đọc, chỉ có phương thức get* được sử dụng. Nếu bạn đọc về Nguyên tắc phân tách giao diện (thư I в CHẤT RẮN), thì rõ ràng là giao diện của chúng ta quá lớn và thực hiện ít nhất hai trách nhiệm khác nhau. Đã đến lúc chia đôi. Phương pháp getById là cần thiết ở cả hai, nhưng khi ứng dụng trở nên phức tạp hơn, việc triển khai nó sẽ khác nhau. Chúng ta sẽ thấy điều này sau một chút. Tôi đã viết về sự vô dụng của phần viết trong một bài viết trước, vì vậy trong bài viết này tôi sẽ đơn giản quên nó đi.

Đối với tôi, phần Đọc dường như không quá vô dụng, vì ngay cả đối với Eloquent cũng có thể có một số cách triển khai ở đây. Đặt tên lớp là gì? Có thể Đọc bàiKho lưu trữ, nhưng với mẫu Kho anh ấy đã có rất ít sự liên quan. Bạn có thể chỉ Đăng truy vấn:

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

Việc triển khai nó với Eloquent khá đơn giản:

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

Giao diện phải được liên kết với việc triển khai, ví dụ như trong Nhà cung cấp dịch vụ ứng dụng:

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

Lớp học này đã hữu ích. Anh ta nhận ra trách nhiệm của mình bằng cách giải tỏa các bộ điều khiển hoặc lớp thực thể. Trong bộ điều khiển, nó có thể được sử dụng như thế này:

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

Phương thức Bài viếtController::lastPosts chỉ yêu cầu thực hiện một số Bài đăngTruy vấn và làm việc với nó. Trong nhà cung cấp chúng tôi đã liên kết Đăng truy vấn với lớp EloquentPostTruy vấn và lớp này sẽ được thay thế vào bộ điều khiển.

Hãy tưởng tượng rằng ứng dụng của chúng ta đã trở nên rất phổ biến. Hàng nghìn người dùng mỗi phút mở trang có các ấn phẩm mới nhất. Các ấn phẩm phổ biến nhất cũng được đọc rất thường xuyên. Cơ sở dữ liệu không xử lý tốt các tải như vậy, vì vậy chúng sử dụng giải pháp tiêu chuẩn - bộ đệm. Ngoài cơ sở dữ liệu, một ảnh chụp nhanh dữ liệu nhất định được lưu trữ trong bộ lưu trữ được tối ưu hóa cho một số hoạt động nhất định - memcached hoặc redis.

Logic bộ nhớ đệm thường không quá phức tạp, nhưng việc triển khai nó trong EloquentPostQueries thì không chính xác lắm (nếu chỉ vì Nguyên tắc trách nhiệm đơn lẻ). Việc sử dụng mẫu sẽ tự nhiên hơn nhiều Trang trí và triển khai bộ nhớ đệm làm vật trang trí cho hành động chính:

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

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

Bỏ qua giao diện Kho trong hàm tạo. Không hiểu vì lý do gì mà họ quyết định đặt tên giao diện cho bộ nhớ đệm trong Laravel theo cách này.

lớp Bài đăng được lưu vào bộ nhớ đệm chỉ thực hiện bộ nhớ đệm. $this->cache->remember kiểm tra xem mục này có trong bộ đệm hay không và nếu không thì gọi lại và ghi giá trị trả về vào bộ đệm. Tất cả những gì còn lại là triển khai lớp này vào ứng dụng. Chúng tôi cần tất cả các lớp trong ứng dụng để yêu cầu triển khai giao diện Đăng truy vấn bắt đầu nhận được một thể hiện của lớp Bài đăng được lưu vào bộ nhớ đệm. Tuy nhiên, bản thân anh Bài đăng được lưu vào bộ nhớ đệm hàm tạo phải nhận một lớp làm tham số EloquentPostTruy vấnvì nó không thể hoạt động nếu không triển khai "thực sự". Chúng tôi thay đổi Nhà cung cấp dịch vụ ứng dụng:

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

Mọi mong muốn của tôi đều được nhà cung cấp mô tả khá tự nhiên. Do đó, chúng tôi đã triển khai bộ nhớ đệm cho các yêu cầu của mình chỉ bằng cách viết một lớp và thay đổi cấu hình vùng chứa. Phần còn lại của mã ứng dụng không thay đổi.

Tất nhiên, để triển khai đầy đủ bộ nhớ đệm, cũng cần phải triển khai tính năng vô hiệu hóa để bài viết đã xóa không bị treo trên trang web một thời gian mà bị xóa ngay lập tức. Nhưng đây là những điều nhỏ nhặt.

Điểm mấu chốt: chúng tôi không sử dụng một mà là hai mẫu. Vật mẫu Phân chia trách nhiệm truy vấn lệnh (CQRS) đề xuất tách biệt hoàn toàn các hoạt động đọc và ghi ở cấp độ giao diện. Tôi đến với anh ấy thông qua Nguyên tắc phân tách giao diện, điều này gợi ý rằng tôi khéo léo vận dụng các mẫu và nguyên tắc và rút ra cái này từ cái kia dưới dạng định lý :) Tất nhiên, không phải mọi dự án đều cần sự trừu tượng như vậy để chọn thực thể, nhưng tôi sẽ chia sẻ thủ thuật với bạn. phát triển, bạn có thể chỉ cần tạo một lớp Đăng truy vấn với cách triển khai thông thường thông qua Eloquent:

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

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

Khi có nhu cầu về bộ nhớ đệm, chỉ với một thao tác đơn giản, bạn có thể tạo một giao diện (hoặc lớp trừu tượng) thay cho lớp này Đăng truy vấn, sao chép phần thực hiện của nó vào lớp EloquentPostTruy vấn và đi đến sơ đồ mà tôi đã mô tả trước đó. Phần còn lại của mã ứng dụng không cần phải thay đổi.

Tất cả những thủ thuật này với các lớp, giao diện, Tiêm phụ thuộc и CQRS được mô tả chi tiết trong cuốn sách của tôi “Kiến trúc của các ứng dụng web phức tạp”. Ngoài ra còn có một giải pháp cho câu đố tại sao tất cả các lớp của tôi trong các ví dụ cho bài viết này đều được đánh dấu là lớp cuối cùng.

Nguồn: www.habr.com

Thêm một lời nhận xét