介紹
Laravel Ddd是一個基於領域驅動設計思想的代碼框架,幫助你快速地建立起符合領域驅動設計的代碼目錄結構。
項目地址
https://github.com/ronghz/laravel-ddd
https://packagist.org/packages/ronghz/laravel-ddd
https://github.com/ronghz/laravel-ddd-demo
安裝
composer require ronghz/laravel-ddd
在config/app.php增加ServiceProvider配置
Ronghz\LaravelDdd\LaravelDddServiceProvider::class,
特別地,需要在\App\Console\Kernel的構造函數裏註冊一下MigrationServiceProvider,才能使用migrate命令來發現放在領域目錄下的遷移腳本。
public function __construct(Application $app, Dispatcher $events)
{
parent::__construct($app, $events);
$app->register(MigrationServiceProvider::class);
}
使用
生成領域目錄和代碼
以文章領域爲例,創建新領域時,先執行php artisan ddd-generator Article
在app/Domain目錄下會生成領域的基本目錄結構。
然後在app/Domain/Article/Models/migrations目錄下編寫這個領域所需要的遷移腳本,如增加一個數據表article_articles表。
執行php artisan migrate
生成數據表,框架已經對migrate命令作了擴展,可以發現領域目錄下的遷移腳本文件。
執行php artisan ddd-generator Article --model=Article --table=articel_articles
,生成Article聚合根的相關類文件。
最終生成目錄和文件如下圖所示:
├── app // 代碼目錄
│ ├── Domain // 領域代碼目錄
│ │ ├── Article // 文章領域
│ │ │ ├── Commands // 領域腳本
│ │ │ ├── Events // 事件
│ │ │ ├── Jobs //
│ │ │ ├── Listeners //
│ │ │ ├── Models // 模塊層
│ │ │ │ ├── migrations // 模塊遷移腳本
│ │ │ │ ├── Article.php
│ │ │ ├── Repositories // 倉庫層
│ │ │ │ ├── ArticleRepository.php
│ │ │ ├── Resources // API資源類
│ │ │ │ ├── ArticleResource.php
│ │ │ ├── Ports // 接口層
│ │ │ │ ├── Cross // 跨域調用接口
│ │ │ │ │ ├── ArticleCrossDomain.php
│ │ │ │ ├── Platform // 平臺管理端接口
│ │ │ │ │ ├── Controllers // 這個端的接口
│ │ │ │ │ │ ├── ArticleController.php
│ │ │ │ │ ├── Requests // 這個端的輸入參數類
│ │ │ │ │ │ ├── ArticleResource.php
│ │ │ │ │ ├── Services // 應用服務
│ │ │ │ │ │ ├── ArticleService.php
│ │ │ │ │ ├── routes.php // 這個端的路由配置
│ │ │ │ ├── Customer // 客戶端接口
│ │ │ ├── Services // 領域服務
│ │ │ │ ├── ArticleService.php
│ │ │ ├── Supports //
│ │ │ │ ├── Enums // 枚舉變量
│ │ │ │ ├── Exceptions// 領域內的異常
配置文件說明 ddd.php
return [
'router' => [
'use_auto_router' => true, //是否使用自動映射路由
'project_prefix' => false, //自動路由是否有項目名稱前綴
'client_version_key' => 'Release-Version', //接口版本請求頭
],
'generator' => [
'ports' => ['Platform', 'Merchant', 'Customer']
],
];
分層職責和調用限制
Controller
Controller放在{domain}/Ports目錄下,是客戶端請求的入口,如果系統包括多個不同的客戶端,應該分開不同的Controller,例如PC端的管理後臺和手機端的App,要分成兩個端。
返回數據時,調用php $this->success($data)
會根據$data裏的Model類調用對應的Resource類組裝數據。
Controller應該只包含參數的校驗和輸出的格式化邏輯,不應該在Controller裏編寫業務邏輯。
routes
路由文件routes.php放在對應的Ports目錄下,約定所有接口的url按{客戶端}/{領域名}/{聚合名}/{其它}的規則來定義,避免不同領域的之間的衝突。
Application Service
應用服務,每個端的Controller會有一個對應的ApplicationService,用來處理這個端獨有的業務邏輯。
ApplicationService不是必須的,允許調用同領域的DomainService和Repository,或者跨域的CrossDomainService。
Domain Service
領域服務,負責領域的業務邏輯。
DomainService可以調用同領域的Repository,禁止直接調用的Model,避免調用CrossDomain。
Repository
封裝數據查詢、變更操作,如findByXXX()方法。
只允許調用同領域的Model。
Model
只需要定義模型的表名、字段名以及關聯關係。
migration文件也按領域目錄放置。
Resource
跟Model一一對應,在Controller的方法裏裏調用$this->success()返回數據時,會自動根據Model的類型調用對應的Resource類。
Cross Domain Service
跨域調用服務,調用其它領域的方法時,必須通過CrossDomainService。
CrossDomainService的地位等同於Controller,可以調用同領域的ApplicationService和DomainService,或者跨域的CrossDomainService。
DTO (未實現)
Data Transfer Object,數據傳輸對象,跨域調用的時候不應該直接返回Model對象,而是應該使用DTO再在不同領域間傳遞數據。
其它特性
自動加載相關類
多數類會根據類名自動加載同一領域下的同一聚合根的相關類,例如ArticleController會自動加載ArticleService,ArticleService會自動加載ArticleRepository。
自動映射路由
可選特性,默認不開啓。如果需要使用,在\App\Http\Kernel裏增加一個Middleware\Ronghz\LaravelDdd\Framework\Middleware\AutoRouter::class
,再把配置文件中的router.use_auto_router設置爲true。
開啓自動映射路由後,可以不在routes.php裏配置路由規則,框架會自動按照{客戶端}/{領域名}/{控制器名}/{方法名}的規則來把url解析到對應的方法。
例如 GET /customer/article/author/index會映射到App\Domain\Article\Ports\Customer\Controllers\AuthorController:getIndex()。
{方法名}後面的segment全部作爲url參數。
url的單詞用中劃線區分,對應的類名和方法名會轉成駝峯格式。
如果url需要加上項目前綴,就把配置文件中的router.project_prefix,自動映射時會把url的第一段識別爲項目名。
同一個url如果在routes.php裏做了配置,會優先使用配置的映射關係,不使用自動路由的映射。
接口版本控制
項目迭代過程中,經常需要對接口進行版本升級,針對不同的客戶端版本返回不同的數據。
框架支持按照客戶端請求頭的版本來做接口版本控制,只要跟客戶端約定接口版本的命名方式以及傳參方式,框架默認通過Release-Version請求頭。
初始情況下,不需要做任何處理。當需要升級接口時,覆蓋對應的Controller的VERSION_RANGE變量,然後再增加一個新版本的方法,新方法的名字用原方法名加上版本號。
例如,文章列表的接口在1.2.3和2.2.1兩個版本做了升級,這時候ArticleController會變成這樣。
class ArticleController extends DddController
{
const VERSION_RANGE = [
'getRange' => ['1.2.3', '2.2.1']
];
public function getRange()
{
echo 'default';
}
public function getRangeV1_2_3()
{
echo '1.2.3';
}
public function getRangeV2_2_1()
{
echo '2.2.1';
}
}
異常處理
枚舉基類
枚舉類型都應該繼承Ronghz\LaravelDdd\Framework\Base\DddEnum基類,這個類裏實現了枚舉名、枚舉值和描述文本三者互相轉換的方法。
單元測試
使用代碼生成器生成代碼的時候,會同時生成領域服務的單元測試類。