上週我寫了
<?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 詳細描述
來源: www.habr.com