基于ABP落地领域驱动设计-01.全景图
什么是領域驅動?
領域驅動設計(簡稱:DDD)是一種針對復雜需求的軟件開發方法。將軟件實現與不斷發展的模型聯系起來,專注于核心領域邏輯,而不是基礎設施細節。DDD適用于復雜領域和大規模應用,而不是簡單的CRUD應用。它有助于建立一個靈活、模塊化和可維護的代碼庫。
OOP 和 SOLID
DDD實現高度依賴面向對象編程思想(OOP)和SOLID原則。實際上,它實現并擴展了這些原則。因此,在真正實施DDD時,對OOP和SOLID的良好理解將對您有很大幫助。
DDD 和 Clean Architecture
一個基于領域驅動的解決方案有四個基本層:
業務邏輯分布在兩個層中:領域層(Domain Layer)和?應用層(Application Layer),分別包含不同類型的業務邏輯:
?領域層:實現領域(或系統)中的用例獨立的核心業務邏輯。?應用層:基于領域的應用程序用例,應用程序用例可以看作是用戶界面上的用戶交互。?展示層:包含應用程序UI元素(頁面、組件等)。?基礎層:支持層,通過對第三方類庫的調用或系統的抽象和集成來實現對其他層的支持。
簡潔架構(Clean Architecture)?是與之相同的分層架構,又稱為洋蔥架構(Onion Architecture)。
從架構圖可以看出,每一層只直接依賴于它內部的層,最獨立的層是領域層,顯示在最內圈中。
核心構件
DDD主要關注領域層和應用層,展示層和基礎層被看作是細節,業務層不應該依賴于它們,但這并不意味著展示層和基礎層不重要,它們也非常重要。展示層中的UI框架和基礎層中的數據提供程序有他們自己的實現規則和最佳實踐,需要了解和應用。然而,這些并不在DDD的主題中,我們重點來看領域層和應用層的基本構件。
領域層構件
?實體(Entity):一個實體是一個對象,該對象包含自己的屬性和方法,屬性用于存儲數據和描述狀態;方法結合屬性實現業務邏輯。一個實體使用唯一標識(ID)來表示,兩個實體對象ID不同則是為不同的實體。?值對象(Value Object):值對象是另一種類型的領域對象,該對象由其屬性而不是唯一ID來標識。意思是說,只有全部屬性相同才會被認為是同一個對象。值對象通常被實現為不可變的,而且大多比實體簡單得多。?聚合和聚合根:聚合根是一個特定類型的實體,具有額外的職責。聚合是以聚合根為中心綁定在一起的一組對象,對象包括實體和值對象。?倉儲(接口):倉儲是一個類似集合的接口,被領域層和應用層用來訪問數據持久化系統(數據庫)。它將數據庫的復雜性從業務代碼中隱藏起來。領域層包含倉儲接口。?領域服務:領域服務是無狀態服務,實現核心領域業務規則。用于實現依賴于多個聚合(實體)或外部服務的領域邏輯。?規約:用于為實體和其他業務對象定義可命名的、可重用的和可組合的過濾器。?領域事件:領域事件是一種低耦合的通知方式,當一個特定的領域事件發生時,會通知其他服務。
應用層構件
?應用服務:應用服務是無狀態服務,實現應用程序用例。一個應用服務通常獲取和返回數據傳輸對象(DTOs),用于展示層。調用領域對象來實現用例。一個用例通常被認為是一個工作單元。?數據傳輸對象(DTO):DTO是簡單對象,不包含任何業務邏輯,只用于在應用層和展示層傳遞數據。?工作單元:一個工作單元是一個原子工作。在工作單元中的所有操作統一提交,要么全部成功,失敗則全部回滾。
項目分層
下面圖片是在 .Net解決方案(Visual Studio),基于 ABP 應用程序啟動模板創建的解決方案結構:
解決方案名稱為:IssueTracking?。解決方案的項目分層考慮到DDD原則,同時兼顧開發和部署實踐而劃分。
示例項目業務場景參考 GitHub 問題追蹤,這個場景比較通用,使用過Git的開發人員都了解。
領域層
領域層拆分為兩個項目:
?IssueTracking.Domain:領域層,該項目包含所有領域層構件,比如:實體、值對象、領域服務、規約、倉儲接口等。?IssueTracking.Domain.Shared:領域共享層,包含屬于領域層,但是與其他層共享的類型。舉個例子:定義的常量和枚舉,既在領域對象中使用,也要在其他層中使用,放在該項目中。
應用層
應用層拆分為兩個項目:
?IssueTracking.Application.Contracts:應用契約層,包含應用服務接口和數據傳輸對象(用于接口),該項目被應用程序客戶端引用,比如:WEB項目、API客戶端項目。?IssueTracking.Application:應用層,實現在 Contracts 項目中定義的接口。
展示層
?IssueTracking.Web:可執行程序,調用應用服務或APIs,當前解決方案中是 ASP.NET Core MVC/Razor Pages 應用。
ABP框架提供不同類型的UI框架,比如:Angular和Blazor。如果采用這種UI框架,解決方案為前后端分離架構,解決方案中不包含 IssueTracking.Web 項目,而是通過 IssueTracking.HttpApi.Host 項目作為一個獨立的端點提供 HTTP API 服務,供客戶端調用。
遠程服務層
?IssueTracking.HttpApi:遠程服務層,該項目用于定義 HTTP APIs,通常包含 MVC Controller 及相關的模型。
大多數時候,API Controller 只是應用服務的包裝器,以便將它們公開給遠程客戶端。因為ABP框架提供根據應用服務接口自動生成API Controller,實現自動配置并將你的應用服務公開為API控制器,所以通常不會在這個項目中創建控制器。
?IssueTracking.HttpApi.Client:遠程服務代理層,客戶端應用程序引用該項目,將直接通過依賴注入使用遠程應用服務,該項目基于ABP Framework動態C#客戶端API代理系統實現。在C#項目中需要調用HTTP APIs時,會非常有用。
在解決方案的?test?文件夾中有一個控制臺應用程序,名為IssueTracking.HttpApi.Client.ConsoleTestApp。它只是使用IssueTracking.HttpApi.Client項目來消費應用程序所暴露的API。它只是一個演示應用程序,可以安全地刪除它。如果認為不需要,甚至可以刪除IssueTracking.HttpApi.Client項目。
基礎層
實現DDD時,可以使用一個基礎層項目來實現所有的集成和抽象,當然也可以為不同依賴創建不同項目。
建議折中處理,為核心基礎依賴創建單獨項目,比如:Entity Framework Core;另外創建一個公共基礎項目存放其他基礎設施。
啟動模板中包含兩個項目對 Entity Framework Core 進行集成:
?IssueTracking.EntityFrameworkCore:EF Core核心基礎依賴項目,包含:數據上下文、數據庫映射、EF Core倉儲實現等。?IssueTracking.EntityFrameworkCore.DbMigrations:數據遷移項目,是一個特殊的工具項目,用于管理 Code First 數據遷移。項目中有獨立的數據上下文,用于數據遷移。除了在需要創建新的數據庫遷移或添加應用程序模塊增加相應的表時,需要創建一個新的數據庫遷移之外,通常不會涉及這個項目。
可能你會疑惑為什么集成EF Core創建了兩個項目,因為模塊化的需要。每一個模塊有其獨立的?DbContext,應用程序也有一個?DbContext。DbMigration項目包含用于跟蹤和應用單個遷移模塊的聯合。雖然大多數時候您不需要了解它,但您可以查看 EF Core遷移文檔,以獲得更多信息。
其他項目
還有一個項目,IssueTracking.DbMigrator,一個簡單的控制臺應用程序,當你執行它時,會遷移數據庫結構并初始化種子數據。這是一個有用的實用程序,可以在開發和生產環境中使用它。
項目依賴關系
下圖是解決方案中項目引用(依賴)關系
前面我們講解了各個項目的作用,接下來梳理項目之前的關系:
?Domain.Shared?其他項目直接或間接引用,項目中定義的類型在所有項目中共享。?Domain?只引用?Domain.Shared,比如:在?Domain.Shared?中定義的?IssuType?枚舉類型需要在?Domain?項目中?Issue?實體中用到。?Application.Contracts?依賴?Domain.Shared,這樣我們可以在 DTOs 中使用這些共享類型。比如:CreateIssueDto中可以直接使用?IssueType?枚舉。?Application?依賴?Application.Contracts?,因為?Application?實現?Application.Contracts?中定義的服務接口和使用 DTO 對象。同時,引用?Domain?項目,在應用服務中使用倉儲接口或領域對象。?EntiryFrameworkCore?依賴?Domain?,映射?Domain?對象(實體和值類型)到數據庫表(ORM)并實現在?Domain?中定義的倉儲接口。?HttpApi?依賴?Application.Contract,在控制器在內部對 應用服務接口 進行依賴注入。?HttpApi.Client?依賴?Application.Contract?消費應用服務?Web?依賴?HttpApi?,發布里面定義的HTTP APIs。另外,通過這種方式,它間接地依賴于?Application.Contracts?項目,可以在頁面/組件中使用應用服務。
虛擬依賴
當你仔細查看解決方案依賴關系圖時,會看到還有兩個依賴關系,在上圖中用虛線表示。Web項目依賴于?Application?和?EntityFrameworkCore?項目,理論上不應該是這樣,但實際上是這樣。
這是因為?Web?是運行和托管應用程序的最終項目,應用程序在運行時需要應用服務和倉儲的實現。
這個設計決定有可能讓你在展示層中使用實體和EF Core 對象,但這應該是嚴格避免的。然而,我們發現替代設計過于復雜。在這里,如果你想消除這種依賴性,有兩個備選方案:
?將 Web 項目轉換為 Razor類庫類型,然后創建新項目,比如:Web.Host,引用 Web 項目、Application 和 EntityFrameworkCore 項目。在新項目中,不需要編寫任何UI代碼,只用來做承載項目。?從 Web 項目中移除 Application 和 EntityFrameworkCore 項目引用,作為 ABP?插件模塊在應用初始化時加載程序集。
DDD應用程序的執行流程
下圖顯示基于DDD模式開發的Web應用請求的基本流程:
?通過UI用戶交互(可以看做是一個用例)發起HTTP請求到服務器
?在展示層 MVC Controller(HTTP API) 或 Razor Page Handler(Razor Pages)接收并處理請求,在此階段執行橫切關注點,如:授權、輸入驗證、異常處理、審計日志、緩存等。Controller或Page在構造函數中注入應用服務接口,調用方法發送和接收DTO對象。?應用服務使用領域對象(實體、倉儲接口、領域服務等)實現用例。在此階段,應用層執行橫切關注點,如:授權、驗證、審計日志、工作單元等。一個應用服務方法是一個工作單元,具有原子性。
大多數橫切關注點在ABP框架中自動實現或按照約定實現,無需額外編寫代碼。
通用原則
在進入DDD之前,讓我們梳理下DDD通用原則。
數據庫(Database Provider / ORM)獨立性原則
領域層和應用層不知道項目中使用的 ORM 和 Database Provider。只依賴于倉儲接口,并且倉儲接口不適合使用用任何 ORM 特殊對象。
這一原則的主要原因是:
1.使領域層和應用層與基礎層獨立,因為基礎層將來可能更改,或者你可能需要支持其他類型數據庫。2.使領域和應用聚焦在業務代碼上,通過將基礎設施實現細節隱藏于倉儲之后,使您的領域和應用服務專注于業務代碼。3.易于自動化測試,因為可以通過倉儲接口模擬倉儲數據。
根據這一原則,除啟動應用程序外,解決方案中的任何項目都沒有引用?EntityFrameworkCore?項目。
關于數據庫獨立性原則的討論
尤其是原因1會深深地影響你的領域對象設計(比如,實體關系)和應用層代碼。假設你當前使用 Entity Framework Core 操作關系型數據庫,后期希望切換為 MongoDB,這就決定你不能使用 EF Core 中獨有功能,因為在MongoDB中不被支持。
舉個例子:
?不能使用更改跟蹤(Change Tacking),因為 MongoDB 不支持。所以,需要顯式更改實體。?不能在實體中使用導航屬性(Navigation Properties)?或集合關聯其他聚合,因為可能在文檔數據庫中不支持。
那么如何解決實體關聯的問題?記住規則:僅通過Id引用其他聚合。
如果你認為這些功能對你很重要,而且你永遠不會棄用 EF Core,我們認為這個原則是可以有彈性的,但是我們仍然建議使用倉儲模式來隱藏基礎設施的實現細節。
ABP Framework 為倉儲接口?IRepository?提供獲取?IQueryable?對象的擴展方法?GetQueryableAsync(),使我們在使用倉儲時可以直接使用標準LINQ擴展方法。
展示技術無關性原則
展示層技術(UI框架)是應用程序中變化最多的部分,將領域層和應用層設計成完全不知道展示層技術或框架是非常重要的。
這一原則相對容易實現,而ABP的啟動模板使其更加容易實現,選擇不同UI框架自動生成對應的啟動模板項目。
在某些場景下,你可能需要在應用層和展示層使用相同的邏輯。舉例,你可能需要在兩個層中進行驗證和授權。在UI層檢測是為了提高用戶體驗,在應用層和領域層是出安全和數據有效性考慮。這是非常正常和必要的。
聚焦狀態變化,而不是性能優化
DDD聚焦領域對象如何變化和如何交互;如何創建實體和改變屬性,并且保持數據的完整性、有效性;如何創建方法,實現業務規則。
DDD沒有考慮報表和大規模查詢等需要高性能的業務場景,如果你的應用程序中沒有花哨的儀表盤或報表功能,誰會去考慮呢?意思是我們需要自己考慮性能問題。
性能優化或技術選型,只要不影響到業務邏輯,可以自由使用 SQL Server 全部功能,比如:查詢優化、索引、存儲過程等技術;甚至使用一個其他數據源,如:ElasticSearch,來負責報表功能。
學習幫助
圍繞DDD和ABP Framework兩個核心技術,后面還會陸續發布核心構件實現、綜合案例實現系列文章,敬請關注!
ABP Framework 研習社(QQ群:726299208)?ABP Framework 學習及實施DDD經驗分享;示例源碼、電子書共享,歡迎加入!
總結
以上是生活随笔為你收集整理的基于ABP落地领域驱动设计-01.全景图的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 基于ABP落地领域驱动设计-04.领域服
- 下一篇: 基于ABP落地领域驱动设计-02.聚合和