Abp vNext 基礎篇丨分層架構
- 2021 年 8 月 19 日
- 筆記
介紹
本章節對 ABP 框架進行一個簡單的介紹,摘自ABP官方,後面會在使用過程中對各個知識點進行細緻的講解。
領域驅動設計
領域驅動設計(簡稱:DDD)是一種針對複雜需求的軟體開發方法。將軟體實現與不斷發展的模型聯繫起來,專註於核心領域邏輯,而不是基礎設施細節。DDD適用於複雜領域和大規模應用,而不是簡單的CRUD應用。它有助於建立一個靈活、模組化和可維護的程式碼庫。
一個基於領域驅動的解決方案有四個基本層:
領域層:實現領域(或系統)中的用例獨立的核心業務邏輯。
應用層:基於領域的應用程式用例,應用程式用例可以看作是用戶介面上的用戶交互。
展示層:包含應用程式UI元素(頁面、組件等)。
基礎層:支援層,通過對第三方類庫的調用或系統的抽象和集成來實現對其他層的支援。
核心構件
DDD主要關注領域層和應用層,展示層和基礎層被看作是細節,業務層不應該依賴於它們,但這並不意味著展示層和基礎層不重要,它們也非常重要。展示層中的UI框架和基礎層中的數據提供程式有他們自己的實現規則和最佳實踐,需要了解和應用。然而,這些並不在DDD的主題中,我們重點來看領域層和應用層的基本構件。
領域層構件
實體(Entity):一個實體是一個對象,該對象包含自己的屬性和方法,屬性用於存儲數據和描述狀態;方法結合屬性實現業務邏輯。一個實體使用唯一標識(ID)來表示,兩個實體對象ID不同則是為不同的實體。
值對象(Value Object):值對象是另一種類型的領域對象,該對象由其屬性而不是唯一ID來標識。意思是說,只有全部屬性相同才會被認為是同一個對象。值對象通常被實現為不可變的,而且大多比實體簡單得多。
聚合和聚合根:聚合根是一個特定類型的實體,具有額外的職責。聚合是以聚合根為中心綁定在一起的一組對象,對象包括實體和值對象。
倉儲(介面):倉儲是一個類似集合的介面,被領域層和應用層用來訪問數據持久化系統(資料庫)。它將資料庫的複雜性從業務程式碼中隱藏起來。領域層包含倉儲介面。
領域服務:領域服務是無狀態服務,實現核心領域業務規則。用於實現依賴於多個聚合(實體)或外部服務的領域邏輯。
規約:用於為實體和其他業務對象定義可命名的、可重用的和可組合的過濾器。
領域事件:領域事件是一種低耦合的通知方式,當一個特定的領域事件發生時,會通知其他服務。
應用層構件
應用服務:應用服務是無狀態服務,實現應用程式用例。一個應用服務通常獲取和返回數據傳輸對象(DTOs),用於展示層。調用領域對象來實現用例。一個用例通常被認為是一個工作單元。
數據傳輸對象(DTO):DTO是簡單對象,不包含任何業務邏輯,只用於在應用層和展示層傳遞數據。
工作單元:一個工作單元是一個原子工作。在工作單元中的所有操作統一提交,要麼全部成功,失敗則全部回滾。
ABP項目分層解析
領域層
領域層拆分為兩個項目:
Bcvp.Blog.Core.Domain:領域層,該項目包含所有領域層構件,比如:實體、值對象、領域服務、規約、倉儲介面等。
Bcvp.Blog.Core.Domain.Shared:領域共享層,包含屬於領域層,但是與其他層共享的類型。舉個例子:定義的常量和枚舉,既在領域對象中使用,也要在其他層中使用,放在該項目中。
應用層
應用層拆分為兩個項目:
Bcvp.Blog.Core.Application.Contracts:應用契約層,包含應用服務介面和數據傳輸對象(用於介面),該項目被應用程式客戶端引用,比如:WEB項目、API客戶端項目。
Bcvp.Blog.Core.Application:應用層,實現在 Contracts 項目中定義的介面。
展示層
Bcvp.Blog.Core.HttpApi.Host 項目作為一個獨立的端點提供 HTTP API 服務,供客戶端調用。
遠程服務層
Bcvp.Blog.Core.HttpApi:遠程服務層,該項目用於定義 HTTP APIs,通常包含 MVC Controller 及相關的模型。
Bcvp.Blog.Core.HttpApi.Client:遠程服務代理層,客戶端應用程式引用該項目,將直接通過依賴注入使用遠程應用服務,該項目基於ABP Framework動態C#客戶端API代理系統實現。在C#項目中需要調用HTTP APIs時,會非常有用。
基礎層
實現DDD時,可以使用一個基礎層項目來實現所有的集成和抽象,當然也可以為不同依賴創建不同項目。
建議折中處理,為核心基礎依賴創建單獨項目,比如:Entity Framework Core;另外創建一個公共基礎項目存放其他基礎設施。
啟動模板中包含兩個項目對 Entity Framework Core 進行集成:
Bcvp.Blog.Core.EntityFrameworkCore:EF Core核心基礎依賴項目,包含:數據上下文、資料庫映射、EF Core倉儲實現等。
其他項目
還有一個項目 Bcvp.Blog.Core.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 項⽬,可以在⻚⾯/組件中使⽤應⽤服務
DDD通用原則
在正式開始之前我們在梳理一下DDD的通用原則。
資料庫(Database Provider / ORM)獨⽴性原則
領域層和應⽤層不知道項⽬中使⽤的 ORM 和 Database Provider。只依賴於倉儲接⼝,並且倉儲接⼝ 不適合使⽤⽤任何 ORM 特殊對象
這⼀原則的主要原因是:
-
使領域層和應⽤層與基礎層獨⽴,因為基礎層將來可能更改,或者你可能需要⽀持其他類型資料庫。
-
使領域和應⽤聚焦在業務程式碼上,通過將基礎設施實現細節隱藏於倉儲之後,使您的領域和應⽤服 務專註於業務程式碼。
-
易於⾃動化測試,因為可以通過倉儲接⼝模擬倉儲數據。
關於資料庫獨⽴性原則的討論
假設你當前使⽤ 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 全部功能。
結語
本節知識點:
- 1.講解Abp和DDD的分層架構介紹
- 2.很重要的知識點DDD聚焦狀態變化而非性能優化
聯繫作者:加群:867095512 @MrChuJiu