Laravel DDD代碼框架

介紹

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基類,這個類裏實現了枚舉名、枚舉值和描述文本三者互相轉換的方法。

單元測試

使用代碼生成器生成代碼的時候,會同時生成領域服務的單元測試類。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章