Repository Pattern and Service Layer
PHP:7.2
Laravel:5.7
在大型的專案之中,如果程式的耦合度太高(比如商業邏輯都塞在 Controller
,或者把 Model
當 Library 用),很容易遇到兩個問題:
- 彈性極低,難以維護
- 不易寫單元測試
光這兩點所衍伸出來的苦頭,就夠所有人吃的了。因此,我們需要透過一些架構將這些商業邏輯一步一步拆分出來,讓整個專案好測試、好調整。
Laravel
的 Service Container 提供了 DI(Dependency injection)
功能,方便我們在各個元件之間自動注入相關的 Class
。
Repository Pattern
Notice:
Model
原則上只留relation
,盡量不要有取資料的邏輯。
Repository Pattern
做法很簡單,概念上就是將 Model
取資料的邏輯拆到 Repository
中,讓 Model
變成真正的 Pure-Model
,而實作上搭配 DI(Dependency injection)
就輕鬆許多。
Model 範例:app/Models/Album.php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Album extends Model
{
protected $primaryKey = 'album_id';
protected $table = 'album';
public function songs()
{
return $this->hasMany('App\Models\Song', 'album_id');
}
}
Repository 範例:app/Repositories/AlbumRepository.php
namespace App\Repositories;
use App\Models\Album;
class AlbumRepository
{
protected $album;
// 透過 DI 注入 Model
public function __construct(Album $album)
{
$this->album = $album;
}
// 取資料邏輯:取得公司為 Sony 的專輯
public function getSonyAlbum($limit = 1)
{
return $this->album
->where('album_company', 'Sony')
->limit($limit)
->get();
}
}
這樣一來,Controller
就不會直接跟 Model
依賴了。
Controller 範例
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Repositories\AlbumRepository;
class TestController extends Controller
{
protected $albumRepo;
透過 DI 注入 Repository
public function __construct(AlbumRepository $albumRepo)
{
$this->albumRepo = $albumRepo;
}
public function test(Request $request)
{
// 直接呼叫 Repository 包裝好的 method
return $this->albumRepo->getSonyAlbum(10);
}
}
Service Layer
雖然已經將 Model
取資料的邏輯拆出來了,可是在真實的應用上,一個 Controller
通常都會一次操作好幾張表,雖然 Model
乾淨了, Controller
卻還是塞滿了 Repository
。因此,我們必須再將真正的商業邏輯封裝成 Service
讓 Controller
使用。
實作也是透過 DI(Dependency injection)
的方式進行。
Service 範例:app/Services/RecommendService.php
namespace App\Services;
use App\Repositories\AlbumRepository;
use App\Repositories\SongRepository;
class RecommendService
{
protected $albumRepo;
protected $songRepo;
// 透過 DI 注入 Repository
public function __construct(AlbumRepository $albumRepo, SongRepository $songRepo)
{
$this->albumRepo = $albumRepo;
$this->songRepo = $songRepo;
}
// 推薦專輯及歌曲
public function get()
{
return [
'album' => $this->albumRepo->getSonyAlbum(),
'song' => $this->songRepo->getValidSong()
];
}
}
Controller 範例
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Services\RecommendService;
class TestController extends Controller
{
protected $recommendService;
透過 DI 注入 Service
public function __construct(RecommendService $recommendService)
{
$this->recommendService = $recommendService;
}
public function test(Request $request)
{
// 直接呼叫 Service 包裝好的 method
return $this->recommendService->get();
}
}
這樣一來,就可以解決不易維護及測試的問題啦。