Eloquent 有用的存儲庫嗎?

上週我寫了 關於 Eloquent 實體的 Repository 範本無用的文章然而,他答應告訴我如何部分地利用它來為他謀取利益。為此,我將嘗試分析該模板通常如何在專案中使用。儲存庫所需的最少方法集:

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

然而,在實際專案中,如果決定使用儲存庫,通常會在其中新增檢索記錄的方法:

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

這些方法可以透過 Eloquent 作用域來實現,但是重載實體類別並承擔獲取自身的責任並不是最好的主意,將這種責任轉移到儲存庫類別似乎是合乎邏輯的。是這樣嗎?我特地直觀地將這個介面分成兩個部分。方法的第一部分將用於寫入操作。

標準的寫入操作是:

  • 新物件的建構與挑戰 發布儲存庫::保存
  • PostRepository::getById、實體操控與召喚 發布儲存庫::保存
  • 挑戰 發布儲存庫::刪除

寫入操作不使用取得方法。在讀取操作中,僅使用 get* 方法。如果您讀到 介面隔離原則 (信 I в SOLID),那麼很明顯我們的介面太大並且至少執行兩個不同的職責。是時候將其除以二了。方法 取得ID 兩者都是必要的,但隨著應用程式變得更加複雜,其實現將會有所不同。我們稍後會看到這一點。我在上一篇文章中寫過 write 部分的無用之處,所以在這篇文章中我將簡單地忘記它。

在我看來,Read 部分並不是那麼無用,因為即使對於 Eloquent 來說,這裡也可能有多種實作。給班級什麼名字?能 閱讀帖子存儲庫,但是對於模板 知識庫 他已經沒有什麼相關性了。你可以只 後置查詢:

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

使用 Eloquent 實作它非常簡單:

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

介面必須與實作相關聯,例如 應用服務提供者:

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

這個類別已經很有用了。他透過減輕控制器或實體類別的負擔來實現自己的責任。在控制器中可以這樣使用:

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

方法 貼文控制器::lastPosts 只是要求一些實施 貼文查詢 並與它一起工作。在我們連結的提供者中 後置查詢 與類別 EloquentPost查詢 這個類別將被替換到控制器中。

讓我們想像一下我們的應用程式已經變得非常流行。每分鐘有數千名使用者開啟包含最新出版物的頁面。最受歡迎的出版物也被經常閱讀。資料庫不能很好地處理此類負載,因此它們使用標準解決方案 - 快取。除了資料庫之外,某些資料快照還儲存在針對某些操作最佳化的儲存中 - memcached的Redis的.

快取邏輯通常沒有那麼複雜,但是在 EloquentPostQueries 中實作它並不是很正確(如果只是因為 單一責任原則)。使用模板更自然 裝飾者 並實現緩存作為主要操作的裝飾:

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

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

忽略介面 知識庫 在構造函數中。由於某種未知的原因,他們決定以這種方式命名 Laravel 中的快取介面。

快取後查詢 僅實現緩存。 $this->快取->記住 檢查該條目是否在快取中,如果不在,則呼叫回調並將傳回值寫入快取。剩下的就是將此類實現到應用程式中。我們需要應用程式中的所有類別來請求介面的實現 後置查詢 開始接收類別的實例 快取後查詢。然而他本人 快取後查詢 建構子必須接收一個類別作為參數 EloquentPost查詢因為如果沒有「真正的」實現它就無法運作。我們改變 應用服務提供者:

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

我所有的願望都在提供者中自然地描述。因此,我們僅透過編寫一個類別並更改容器配置來實現對請求的快取。應用程式程式碼的其餘部分沒有改變。

當然,要完全實現緩存,還需要實現失效,使被刪除的文章不會在網站上掛一段時間,而是立即被刪除。但這些都是小事。

底線:我們使用的不是一個模板,而是兩個模板。樣本 指令查詢職責分離 (CQRS) 提出在介面層級完全分離讀寫操作。我透過 介面隔離原則,這表明我熟練地操縱模式和原則,並從另一個推導出另一個作為定理:)當然,並不是每個項目都需要這樣的抽象來選擇實體,但我會與您分享技巧。開發時,只需建立一個類別即可 後置查詢 透過 Eloquent 進行通常的實作:

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

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

當需要快取時,透過一個簡單的操作,您可以建立一個介面(或抽象類別)來代替此類 後置查詢,將其實作複製到類別中 EloquentPost查詢 並轉到我之前描述的方案。其餘的應用程式程式碼不需要更改。

所有這些關於類別、介面的技巧, 依賴注入 и 連續QRS 詳細描述 我的書《複雜 Web 應用程式的架構》。為什麼本文範例中的所有類別都被標記為最終類,這個謎題也有一個解決方案。

來源: www.habr.com

添加評論