為了讓 laravel 更貼近 DDD 的架構,會需要調整一些 Laravel 的專案架構,這部分你必須熟悉 Laravel 的容器運作、生命週期,如果你還不熟悉,建議先讀過 Laravel 原始碼分析。
還有,接下來的內容會假設你對 DDD 已經有一定程度的理解,如果還沒有,建議先讀過 Think in Domain-Driven Design 。
Laravel 專案所有最被關注的東西都放在 app 資料夾底下,這件事就已經跟 DDD 的 Domain、Bounded Context 牴觸了,我個人是額外建立一個新的命名空間(資料夾)來放不同 Bounded Context 的程式,裡面每一個子目錄內容都跟原本 app 裡的架構差不多,原本 app 裡面有的 Http、Services、Providers ... 的目錄,在各個 Bounded Context 內都有。
這個部分可以考慮使用 laravel-modules 來實現類似的目的,如果你跟我一樣不想裝這個套件,那會比較辛苦一點,因為你需要寫一些 Provider 把各個 Bounded Context 內的 migrations、routes、configs .... 的程式在初始化時註冊到 laravel 中。
現在你的專案架構應該會像這樣:
modules 目錄裡的各個 module 其實就是對應到一個 Bounded Context,當然你想要用別的命名也是可以,總之目的是要把系統要解決的問題切分開來。
這邊附上我的 AppServiceProvider 的片段,給大家參考:
<?php
namespace App\Providers;
use File;
use Illuminate\Support\ServiceProvider;
use Illuminate\Database\Eloquent\Factory;
class AppServiceProvider extends ServiceProvider
{
// .... 省略
protected function registerEloquentFactories()
{
// 預設 factories 如果有用到記得載入,或是乾脆不要用
$this->app->make(Factory::class)->load(base_path('database/factories'));
foreach (config('modules') as $module) {
$moduleFactoriesDir = base_path('modules/' . $module['name'] .'/Database/factories');
if (File::isDirectory($moduleFactoriesDir)) {
$this->app->make(Factory::class)->load($moduleFactoriesDir);
}
}
}
protected function reqisterMigrations()
{
$directories = [
// 預設 migration 如果有用到記得載入,或是乾脆不要用
base_path('database/migrations')
];
foreach (config('modules') as $module) {
$moduleMigrationsDir = base_path('modules/' . $module['name'] .'/Database/migrations');
if (File::isDirectory($moduleMigrationsDir)) {
array_push($directories, $moduleMigrationsDir);
}
}
$this->loadMigrationsFrom($directories);
}
}
}
上面那段程式的思路很簡單,其實就是建立一個 modules config,每當有新的 module(Bounded Context) 時,就在 modules config 裡增加一組設定,然後 AppServiceProvider 在 register
的時候把該載入的內容載入好。