超越框架:在 Laravel 專案中實踐 DDD 與 Clean Architecture
許多開發者在處理中大型企業專案時,經常面臨程式碼混亂、難以維護、業務邏輯與框架緊密耦合的困境。一個專案從最初的快速開發階段,逐漸演變成一座難以撼動的技術債務大山。這篇文章將深入探討如何藉由 領域驅動設計 (DDD) 和 潔淨架構 (Clean Architecture),並結合 DevOps 容器化實踐,在功能強大的 Laravel 框架中,建立一個可擴展、易於測試、且高度可維護的企業級後端系統。
您可以透過以下 GitHub 連結檢閱本專案的原始碼:https://github.com/BpsEason/Enterprise-Media-Platform-Blueprint.git
一、為什麼我們需要架構?從 Laravel 的慣例說起
Laravel 框架以其優雅的語法和豐富的功能,讓開發者能夠快速建立應用程式。然而,它的「開箱即用」慣例(如將業務邏輯直接寫在 Controller 或 Model 中)對於小型專案來說非常方便,但在複雜的企業級應用中,這會逐漸導致以下問題:
業務邏輯與展示層耦合:Controller 承擔了太多責任,使得業務邏輯難以重用和測試。
資料庫與業務邏輯耦合:Eloquent ORM 的 Model 既是資料庫的表結構,又被視為業務實體,導致業務規則被綁定在特定資料庫實現上。
缺乏明確的職責劃分:隨著專案成長,新的功能不斷堆積,最終使得程式碼變得臃腫且難以理解。
要解決這些問題,我們需要一套強健的架構設計來重新定義程式碼的職責與邊界。這正是 DDD 和 Clean Architecture 所扮演的角色。
二、領域驅動設計 (DDD):以業務為核心的設計方法
DDD 的核心思想是將程式碼的設計與企業的業務領域緊密結合,並使用開發者與領域專家共同理解的 統一語言 (Ubiquitous Language)。在我們的「企業媒體平台」藍圖中,DDD 幫助我們將專案拆解成以下核心概念:
實體 (Entities):具有唯一標識和生命週期的領域物件。例如,
Article
(文章)就是一個實體,它有唯一的 ID。在我們的專案中,app/Domain/Article.php
定義了文章的核心屬性與行為(如publish
方法),使其成為一個有生命週期的領域物件,而非僅僅是資料庫的一行紀錄。值物件 (Value Objects):描述事物的物件,但沒有唯一標識,其屬性定義了其身份。例如,
ArticleId
(文章 ID)、ArticleStatus
(文章狀態)就是值物件。它們是不可變的,能確保數據的一致性。專案中的app/ValueObjects/ArticleId.php
和app/ValueObjects/ArticleStatus.php
體現了這一點。聚合根 (Aggregates):一個或多個相關聯的實體和值物件組成的集群,並以一個實體作為根來保護其內部的完整性。
Article
可以被視為一個聚合根,所有對其內部屬性(如標題、內容、狀態)的修改,都必須通過Article
實體本身的方法來完成。儲存庫 (Repositories):提供了一個抽象的介面,用於持久化、查詢和重建領域物件。DDD 倡導的是介面(如
app/Domain/IArticleRepository.php
),而不是具體的實現。這使得我們可以輕鬆切換不同的資料庫後端(如 PostgreSQL、Elasticsearch 或 BigQuery),而無需修改上層的業務邏輯。
三、潔淨架構 (Clean Architecture):隔離依賴,保護核心
Clean Architecture 的核心原則是 依賴規則 (The Dependency Rule):原始碼的依賴必須只指向內層。外層可以依賴內層,但內層絕對不能依賴外層。這形成了一個同心圓結構,將應用程式劃分為不同的層次:
實體 (Entities):最內層,包含企業級的業務邏輯與實體(如
Article
)。它們是獨立於任何外部框架或資料庫的。應用層 (Application Use Cases):此層包含應用程式特定的業務邏輯。它協調實體之間的互動來完成特定的用例(Use Cases)。在我們的專案中,
app/Application/ArticleService.php
就是應用服務,它依賴IArticleRepository
介面,而非具體的儲存庫實現。介面轉接器 (Interface Adapters):此層將內層的資料格式轉換為外部框架所需的格式。例如,
app/Http/Controllers/ArticleController.php
將 HTTP 請求轉換為應用服務所需的輸入資料(CreateArticleDto
),並將應用服務的結果轉換為 JSON 格式的 HTTP 回應。框架與外部服務 (Frameworks & External Services):最外層,包含 Laravel 框架本身、資料庫、HTTP 服務、外部 API 等。這層的職責是提供工具和服務,以便內層使用。專案中的
app/Infrastructure/Persistence/BigQueryArticleRepository.php
就是這一層的典型例子,它實現了IArticleRepository
介面,並使用 BigQuery 的客戶端庫來完成資料持久化。
四、在 Laravel 專案中整合 DDD 與 Clean Architecture
以下是我們的專案如何將這兩種設計模式無縫地融入 Laravel:
控制器 (Controller) 的職責:Controller 應該被視為「介面轉接器」。它的唯一職責是接收 HTTP 請求,將其資料驗證與轉換後傳給應用服務,然後將應用服務的結果轉譯成 HTTP 回應。它不應該包含任何業務邏輯。
服務容器 (Service Container) 的妙用:Laravel 的服務容器是實現依賴反轉(Dependency Inversion)的關鍵。我們可以在
AppServiceProvider
中綁定抽象介面(IArticleRepository
)與具體實現(PostgresArticleRepository
或BigQueryArticleRepository
),這使得應用服務可以在執行時動態地取得對應的儲存庫,實現靈活切換。自訂 Artisan 命令:專案中的
app/Console/Commands/SetupQueueCommand.php
體現了基礎設施的自動化管理。這類命令可以將環境設定從手動操作中解耦,讓開發者可以專注於業務邏輯。
五、DevOps 實踐:容器化與 CI/CD
一個優良的架構需要一個穩固的基礎設施來運行。DevOps 實踐確保了從開發到部署的整個流程是自動化且一致的。
Docker 實現環境一致性:
docker-compose.yml
檔案定義了一個完整的開發環境,包含應用程式、資料庫、快取、佇列與搜尋服務。這確保了每個開發者的本地環境與正式環境幾乎完全一致,從而消除「在我的機器上可以執行」的問題。CI/CD 流程自動化:
./github/workflows/main.yml
定義了一個 CI/CD 管道,它在程式碼提交時會自動執行以下任務:靜態分析 (
phpstan
):在執行程式碼前發現潛在的錯誤。程式碼格式化 (
php-cs-fixer
):確保整個團隊的程式碼風格一致。單元與功能測試 (
phpunit
):自動驗證業務邏輯的正確性。Docker 映像建置與推送:如果測試通過,則自動建立新的 Docker 映像檔並推送到容器倉庫。
Kubernetes 實現高可用與擴展:專案的
k8s/
目錄包含了 Kubernetes 的部署配置。這使得應用程式可以在正式環境中輕鬆部署,並利用 Kubernetes 的能力實現 水平自動擴展 (HPA)、服務發現 和 自我修復,以應對高併發流量。
總結
在 Laravel 專案中實踐 DDD 和 Clean Architecture,並非意味著要拋棄 Laravel 本身。相反,是善用 Laravel 提供的服務容器、Artisan 命令和優雅的程式碼結構,將業務邏輯從框架中抽離,使其成為獨立且可測試的核心。
這種架構結合 DevOps 實踐的優勢顯而易見:
高可維護性:程式碼職責清晰,易於理解與修改。
高可擴展性:底層技術(如資料庫)可以輕鬆更換而不影響核心業務邏輯。
高可測試性:業務邏輯單元獨立,可以進行高效的單元測試。
團隊協作:清晰的邊界讓不同團隊可以專注於各自的領域,並使用統一的語言溝通。
這不僅僅是一種寫程式的方法,更是一種思維的轉變,讓您的專案能夠從初期的快速開發,穩健地成長為一個能應對未來挑戰的企業級應用。
沒有留言:
張貼留言