结合eShopOnWeb全面认识领域模型架构
一.項目分析
在上篇中介紹了什么是"干凈架構(gòu)",DDD符合了這種干凈架構(gòu)的特點,重點描述了DDD架構(gòu)遵循的依賴倒置原則,使軟件達到了低藕合。eShopOnWeb項目是學(xué)習(xí)DDD領(lǐng)域模型架構(gòu)的一個很好案例,本篇繼續(xù)分析該項目各層的職責(zé)功能,主要掌握ApplicationCore領(lǐng)域?qū)觾?nèi)部的術(shù)語、成員職責(zé)。
1. web層介紹
eShopOnWeb項目與Equinox項目,雙方在表現(xiàn)層方面對比,沒有太大區(qū)別。都是遵循了DDD表現(xiàn)層的功能職責(zé)。有一點差異的是eShopOnWeb把表現(xiàn)層和應(yīng)用服務(wù)層集中在了項目web層下,這并不影響DDD風(fēng)格架構(gòu)。
項目web表現(xiàn)層引用了ApplicationCore領(lǐng)域?qū)雍虸nfrastructure基礎(chǔ)設(shè)施層,這種引用依賴是正常的。引用Infrastructure層是為了添加EF上下文以及Identity用戶管理。?引用ApplicationCore層是為了應(yīng)用程序服務(wù)?調(diào)用?領(lǐng)域服務(wù)處理領(lǐng)域業(yè)務(wù)。
在DDD架構(gòu)下依賴關(guān)系重點強調(diào)的是領(lǐng)域?qū)拥莫毩?#xff0c;領(lǐng)域?qū)邮峭膱A中最核心的層,所以在eShopOnWeb項目中,ApplicationCore層并沒有依賴引用項目其它層。再回頭看Equinox項目,領(lǐng)域?qū)右膊恍枰蕾囈庙椖科渌鼘印?/p>
下面web混合了MVC和Razor,結(jié)構(gòu)目錄如下所示:
(1) Health checks
Health checks是ASP.NET Core的特性,用于可視化web應(yīng)用程序的狀態(tài),以便開發(fā)人員可以確定應(yīng)用程序是否健康。運行狀況檢查端點/health。
//添加服務(wù)services.AddHealthChecks()
.AddCheck<HomePageHealthCheck>("home_page_health_check")
.AddCheck<ApiHealthCheck>("api_health_check");
//添加中間件
app.UseHealthChecks("/health");
?下圖檢查了web首頁和api接口的健康狀態(tài),如下圖所示
(2) Extensions
向現(xiàn)有對象添加輔助方法。該Extensions文件夾有兩個類,包含用于電子郵件發(fā)送和URL生成的擴展方法。
(3) 緩存
對于Web層獲取數(shù)據(jù)庫的數(shù)據(jù),如果數(shù)據(jù)不會經(jīng)常更改,可以使用緩存,避免每次請求頁面時,都去讀取數(shù)據(jù)庫數(shù)據(jù)。這里用的是本機內(nèi)存緩存。
//緩存接口類private readonly IMemoryCache _cache;
// 添加服務(wù),緩存類實現(xiàn)
services.AddScoped<ICatalogViewModelService, CachedCatalogViewModelService>();
//添加服務(wù),非緩存的實現(xiàn)
//services.AddScoped<ICatalogViewModelService, CatalogViewModelService>();
2. ApplicationCore層
ApplicationCore是領(lǐng)域?qū)?#xff0c;是項目中最重要最復(fù)雜的一層。ApplicationCore層包含應(yīng)用程序的業(yè)務(wù)邏輯,此業(yè)務(wù)邏輯包含在領(lǐng)域模型中。領(lǐng)域?qū)又R在Equinox項目中并沒有講清楚,這里在重點解析領(lǐng)域?qū)觾?nèi)部成員,并結(jié)合項目來說清楚。
下面講解領(lǐng)域?qū)觾?nèi)部的成員職責(zé)描述定義,參考了“Microsoft.NET企業(yè)級應(yīng)用架構(gòu)設(shè)計 第二版”。
領(lǐng)域?qū)觾?nèi)部包括:領(lǐng)域模型和領(lǐng)域服務(wù)二大塊。涉及到的術(shù)語:
? ? ? ? ? ? ? ? ? ? 領(lǐng)域模型(模型)
? 1)模塊
????????????????? ? 2)領(lǐng)域?qū)嶓w(也叫"實體")
????????????????? ? 3)值對象
????????????????? ? 4)聚合
????????????????? ? 領(lǐng)域服務(wù)(也叫"服務(wù)")
????????????????? ? 倉儲
下面是領(lǐng)域?qū)又饕某蓡T:
下面是聚合與領(lǐng)域模型的關(guān)系。最終領(lǐng)域模型包含了:聚合、單個實體、值對象的結(jié)合。
(1) 領(lǐng)域模型
領(lǐng)域模型是提供業(yè)務(wù)領(lǐng)域的概念視圖,它由實體和值對象構(gòu)成。在下圖中Entities文件夾是領(lǐng)域模型,可以看到包含了聚合、實體、值對象。
1.1 模塊
模塊是用來組織領(lǐng)域模型,在.net中領(lǐng)域模型通過命令空間組織,模塊也就是命名空間,用來組織類庫項目里的類。比如:
namespace Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregatenamespace Microsoft.eShopWeb.ApplicationCore.Entities.BuyerAggregate
1.2 實體
實體通常由數(shù)據(jù)和行為構(gòu)成。如果要在整個生命周期的上下文里唯一跟蹤它,這個對象就需要一個身份標識(ID主鍵),并看成實體。 如下所示是一個實體:
/// <summary>/// 領(lǐng)域?qū)嶓w都有唯一標識,這里用ID做唯一標識
/// </summary>
public class BaseEntity
{
public int Id { get; set; }
}
/// <summary>
/// 領(lǐng)域?qū)嶓w,該實體行為由Basket聚合根來操作
/// </summary>
public class BasketItem : BaseEntity
{
public decimal UnitPrice { get; set; }
public int Quantity { get; set; }
public int CatalogItemId { get; set; }
}
? 1.3 值對象
值對象和實體都由.net 類構(gòu)成。值對象是包含數(shù)據(jù)的類,沒有行為,可能有方法本質(zhì)上是輔助方法。值對象不需要身份標識,因為它們不會改變狀態(tài)。如下所示是一個值對象
1.4 聚合
在開發(fā)中單個實體總是互相引用,聚合的作用是把相關(guān)邏輯的實體組合當(dāng)作一個整體對待。聚合是一致性(事務(wù)性)的邊界,對領(lǐng)域模型進行分組和隔離。聚合是關(guān)聯(lián)的對象(實體)群,放在一個聚合容器中,用于數(shù)據(jù)更改的目的。每個聚合通常被限制于2~3個對象。聚合根在整個領(lǐng)域模型都可見,而且可以直接引用?! ?/p>
在該項目中領(lǐng)域模型與“Microsoft.NET企業(yè)級應(yīng)用架構(gòu)設(shè)計第二版”書中描述的職責(zé)有不一樣地方,來看一下:
(1) 領(lǐng)域服務(wù)有直接引用聚合中的實體(如:BasketItem)。書中描述是聚合中實體不能從聚合之處直接引用,應(yīng)用把聚合看成一個整體。
(2) 領(lǐng)域?qū)嶓w幾乎都是貧血模型。書中描述是領(lǐng)域?qū)嶓w應(yīng)該包括行為和數(shù)據(jù)。
(2) 領(lǐng)域服務(wù)
領(lǐng)域服務(wù)類方法實現(xiàn)領(lǐng)域邏輯,不屬于特定聚合中(聚合是屬于領(lǐng)域模型的),很可能跨多個實體。當(dāng)一塊業(yè)務(wù)邏輯無法融入任何現(xiàn)有聚合,而聚合又無法通過重新設(shè)計適應(yīng)操作時,就需要考慮使用領(lǐng)域服務(wù)。下圖是領(lǐng)域服務(wù)文件夾:
在該項目與“Microsoft.NET企業(yè)級應(yīng)用架構(gòu)設(shè)計第二版”書中描述的領(lǐng)域服務(wù)職責(zé)不完全一樣,來看一下:
(1) 項目中,領(lǐng)域服務(wù)只是用來執(zhí)行領(lǐng)域業(yè)務(wù)邏輯,包括了訂單服務(wù)OrderService和購物車服務(wù)BasketService。書中描述是可能跨多個實體。當(dāng)一塊業(yè)務(wù)邏輯無法融入任何現(xiàn)有聚合。
總的來說,eShopOnWeb項目雖然沒有完全遵循領(lǐng)域?qū)又?#xff0c;成員職責(zé)描述,但可以理解是在代碼上簡化了領(lǐng)域?qū)拥膹?fù)雜性。
? (3) 倉儲
倉儲是協(xié)調(diào)領(lǐng)域模型和數(shù)據(jù)映射層的組件。倉儲是領(lǐng)域服務(wù)中最常見類型,它負責(zé)持久化。倉儲接口的實現(xiàn)屬于基礎(chǔ)設(shè)施層。倉儲通常基于一個IRepository接口。 下面看下項目定義的倉儲接口。
/// <summary>/// T是領(lǐng)域?qū)嶓w,是BaseEntity類型的實體
/// </summary>
/// <typeparam name="T"></typeparam>
public interface IAsyncRepository<T> where T : BaseEntity
{
Task<T> GetByIdAsync(int id);
Task<IReadOnlyList<T>> ListAllAsync();
//使用領(lǐng)域規(guī)則查詢
Task<IReadOnlyList<T>> ListAsync(ISpecification<T> spec);
Task<T> AddAsync(T entity);
Task UpdateAsync(T entity);
Task DeleteAsync(T entity);
//使用領(lǐng)域規(guī)則查詢
Task<int> CountAsync(ISpecification<T> spec);
}
(4) 領(lǐng)域規(guī)則
在倉儲設(shè)計查詢接口時,可能還會用到領(lǐng)域規(guī)則。 在倉儲中一般都是定義固定的查詢接口,如上面?zhèn)}儲的IAsyncRepository所示。而復(fù)雜的查詢條件可能需要用到領(lǐng)域規(guī)則。在本項目中通過強大Linq 表達式樹Expression?來實現(xiàn)動態(tài)查詢。
? 最后Interfaces文件夾中定義的接口,都由基礎(chǔ)設(shè)施層來實現(xiàn)。如:
???????? IAppLogger日志接口
???????? IEmailSender郵件接口
??????? ? IAsyncRepository倉儲接口
3.Infrastructure層
基礎(chǔ)設(shè)施層Infrastructure依賴于ApplicationCore,這遵循依賴倒置原則(DIP),Infrastructure中代碼實現(xiàn)了ApplicationCore中定義的接口(Interfaces文件夾)。該層沒有太多要講的,功能主要包括:使用EF Core進行數(shù)據(jù)訪問、Identity、日志、郵件發(fā)送。與Equinox項目的基礎(chǔ)設(shè)施層差不多,區(qū)別多了領(lǐng)域規(guī)則。
? ? ? ?領(lǐng)域規(guī)則SpecificationEvaluator.cs類用來構(gòu)建查詢表達式(Linq expression),該類返回IQueryable<T>類型。IQueryable接口并不負責(zé)查詢的實際執(zhí)行,它所做的只是描述要執(zhí)行的查詢。
參考資料
Microsoft.NET企業(yè)級應(yīng)用架構(gòu)設(shè)計 第二版
原文地址:https://www.cnblogs.com/MrHSR/p/10869911.html
.NET社區(qū)新聞,深度好文,歡迎訪問公眾號文章匯總?http://www.csharpkit.com?
總結(jié)
以上是生活随笔為你收集整理的结合eShopOnWeb全面认识领域模型架构的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ML.NET机器学习、API容器化与Az
- 下一篇: 微软开源Bing搜索背后的关键算法