什么是倉庫模式(The Repository Pattern)麻裁?
Use a repository to separate the logic that retrieves the data and maps it to the entity model from the business logic that acts on the model. The business logic should be agnostic to the type of data that comprises the data source layer. (…) The repository mediates between the data source layer and the business layers of the application. It queries the data source for the data, maps the data from the data source to a business entity, and persists changes in the business entity to the data source. A repository separates the business logic from the interactions with the underlying data source or Web service.?—??mdsn.microsoft.com
簡單來說, 倉庫模式就是一種存放數(shù)據(jù)訪問邏輯的容器, 它向業(yè)務(wù)層屏蔽了數(shù)據(jù)訪問邏輯的細節(jié), 在不清楚數(shù)據(jù)層設(shè)計結(jié)構(gòu)的情況下, 我們也能按照業(yè)務(wù)邏輯來訪問數(shù)據(jù)層箍镜。
如圖:
可能你會疑問,檢索數(shù)據(jù)并映射到實體模型煎源,這不是 Eloquent 做的嗎?Eloquent 的功能確實如此香缺,但它不是倉庫模式手销,而是 ORM(Object-Relational Mapper),它只是讓我們以面向?qū)ο蟮姆绞皆L問數(shù)據(jù)庫更容易图张,通過使用描述對象和數(shù)據(jù)庫之間映射的元數(shù)據(jù)锋拖,將程序中的對象自動持久化到關(guān)系數(shù)據(jù)庫中。
一些倉庫模式的問題
我看過很多文章中倉庫模式是這么實現(xiàn)的:
<?php
namespace App\Repositories;
class EloquentUserRepository implements UserRepository {
public function getAllUsers() {
return $this->model->all();
}
public function getUser($id) {
return $this->model->find($id);
}
}
這段代碼有什么問題呢祸轮?第一個錯誤是:方法的命名兽埃。因為我們已知我們需要訪問的就是userRepository
,所以方法中永遠不應(yīng)該存在user
這樣的關(guān)鍵字适袜。更好的方式是:
$userRepository->all();
$userRepository->find($id);
因為這些文章的誤導(dǎo)柄错,開發(fā)者可能會認為可以添加一個類似于這樣的方法:
public function getAdults()
{
$users = $this->model->where("age", >=, 18)->get();
return $users;
}
這是錯誤的,因為它只實現(xiàn)了業(yè)務(wù)邏輯,與數(shù)據(jù)庫本身無關(guān)售貌,不符合倉庫模式的設(shè)計哲學(xué)给猾。
上述代碼還有一個錯誤是:在倉庫中返回 Eloquent 模型,這會使你的業(yè)務(wù)邏輯層跟 Eloquent 耦合颂跨。而且敢伸,一開始就建立倉庫是沒有意義的,它只是 Eloquent 查詢的抽象恒削,根據(jù)定義池颈,ORM 抽象不是倉庫模式。
那么钓丰,如果返回自定義的對象并且在上層邏輯中不再使用 Eloquent 呢躯砰?這種方式當然可以,但是這會讓你不能使用 Laravel 中很多重要的功能斑粱。
這個倉庫模式的例子只是一個簡單的demo弃揽,還有一些更高級的實現(xiàn)是先寫倉庫的接口,然后實現(xiàn)這個接口则北,并注冊到服務(wù)提供者矿微,這種方式確實看起來更完美了,但是也更復(fù)雜了尚揣。
在 Laravel 中文官方文檔中涌矢,推薦的最佳實踐有說,“絕不 使用 Repository快骗,因為我們不是在寫 JAVA 代碼娜庇,太多封裝就成了「過度設(shè)計(Over Designed)」,極大降低了編碼愉悅感方篮,使用 MVC 夠傻夠簡單”名秀。他們也確實遵循了, learnku開源論壇的代碼 中藕溅,沒有使用倉庫模式匕得,但是也足夠優(yōu)雅,可讀性絲毫不差巾表。
Service層
可能有人會問汁掠,“那如果不使用倉庫模式,怎么讓 controllers 更瘦呢”集币?其實仔細想想考阱,這是個偽命題。如果你是正確的使用了倉庫模式鞠苟,controllers 其實不會變得更瘦乞榨。因為 Repository 只不過是一個特定的持久化適配器秽之,它不應(yīng)該實現(xiàn)任何業(yè)務(wù)邏輯和應(yīng)用程序邏輯。
要想 controllers 變瘦姜凄,應(yīng)該使用 Service 層政溃。
摘一段 quora 上的回答:
A “Service Layer” exists between the UI and the backend systems that store data and is in charge of managing the business rules of transforming and translating data between those two layers.?—?Cliff Gilley, Quora
Service 層位于表示層和數(shù)據(jù)庫層之間,所以這是放置所有業(yè)務(wù)邏輯的地方态秧。Laravel 應(yīng)用中一般會包含以下4層:
- UI
- Controller
- Service
- Database/Eloquent
一個簡單的 service 可能長這樣:
class UserService
{
protected $user;
public function __construct(User $user)
{
$this->user = $user;
}
public function create(array $attributes)
{
$user = $this->user->newInstance();
$user->fill($attributes)
$user->save();
return $user;
}
}
在 controller 中依賴注入:
class UserController extends Controller
{
protected $userService;
public function __construct(UserService $userService)
{
$this->userService = $userService;
}
public function store(CreateUserRequest $request)
{
$user = $this->userService->create(
$request->except(['_token'])
);
return redirect()->route('user.show', ['id' => $user->id]);
}
}
假設(shè)董虱,之后如果加了一個需求,用戶年齡小于18歲申鱼,將用戶的 status 字段變成0愤诱。
如果你是將這段業(yè)務(wù)邏輯放在 repository 中,那么就打破了 repository 中不能實現(xiàn)業(yè)務(wù)邏輯的規(guī)則了捐友。但如果你用的是 Service淫半,那只需要改變 UserService 中的 create 方法即可:
public function create(array $attributes)
{
$user = $this->user->newInstance();
if($attributes->age < 18) {
$attributes->status = 0;
}
$user->fill($attributes)
$user->save();
return $user;
}
當然,之前的 getAdults 方法也能放在 UserService 里面匣砖。
總結(jié)
如果是一些簡單的應(yīng)用科吭,service 層甚至也可以不需要,查詢邏輯放在 Model 中就好了猴鲫。還可以利用 Trait 來精簡邏輯代碼量对人,提高可讀性。
如果項目比較復(fù)雜拂共,那么service 層是必須的牺弄,如果你仍然要引入 repository, 比如 l5-repository宜狐,那么推薦這樣使用:
public function getAdults() {
$users = $this->userRepo->where('age', >=, 18)->get();
return $users;
}