日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

基于ABP落地领域驱动设计-03.仓储和规约最佳实践和原则

發布時間:2023/12/4 编程问答 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 基于ABP落地领域驱动设计-03.仓储和规约最佳实践和原则 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

dotNET兄弟會?

專注.Net開源技術及跨平臺開發!致力于構建完善的.Net開放技術文庫!為.Net愛好者提供學習交流家園!

公眾號 ?

圍繞DDDABP Framework兩個核心技術,后面還會陸續發布核心構件實現綜合案例實現系列文章,敬請關注!?ABP Framework 研習社(QQ群:726299208)?ABP Framework 學習及實施DDD經驗分享;示例源碼、電子書共享,歡迎加入!

系列文章

基于ABP落地領域驅動設計-01.全景圖基于ABP落地領域驅動設計-02.聚合和聚合根的最佳實踐和原則

倉儲

倉儲(接口)是一組集合的接口,被領域層和應用層用來訪問數據持久化系統(數據庫),以讀寫業務對象,業務對象通常是聚合。

倉儲的通用原則

?在領域層中定義倉儲接口,在基礎層中實現倉儲接口(比如:EntityFrameworkCore項目或MongoDB項目)?倉儲不包含業務邏輯,專注數據處理。?倉儲接口應該保持?數據提供程序/ORM 獨立性。舉個例子,倉儲接口定義的方法不能返回?DbSet?對象,因為該對象由 EF Core 提供,如果使用?MongoDB?數據庫則無法實現該接口。?為聚合根創建對應倉儲,而不是所有實體。因為子集合實體(聚合)應該通過聚合根訪問。

倉儲中不包含領域邏輯

雖然這個規則一開始看起來很好理解,但在實際開發過程中,很容易在不經意間將業務邏輯放到倉儲中。

示例:從倉儲中獲取?inactive?狀態的 Issue

using System; using System.Collections.Generic; using System.Threading.Tasks; using Volo.Abp.Domain.Repositories;namespace IssueTracking.Issues {public interface IIssueRepository:IRepository<Issue,Guid>{Task<List<Issue>> GetInActiveIssuesAsync();} }

IIssueRepository?繼承?IRepository<Issue,Guid>?接口,添加了?GetInActiveIssuesAsync()?方法。與之對應的聚合根類型是?Issue?類:

public class Issue:AggregateRoot<Guid>,IHasCreationTime {public bool IsClosed{get;private set;}public Guid? AssignedUserId{get;private set;}public DateTime CreationTime{get;private set;}public DateTime? LastCommentTime{get;private set;} }

規則要求我們:倉儲不應該知道業務規則,那么問題來了:什么是 inactive Issue(未激活的問題)?這是業務規則

為了更好地理解,我們繼續看看接口方法的實現:

using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using IssueTracking.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using Volo.Abp.Domain.Repositories.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore;namespace IssumeTracking.Issues {public class EfCoreIssueRepository:EfCoreRepository<IssueTrackingDbContext,Issue,Guid>,IIssueRepository{public EfCoreIssueRepository(IDbContextProvider<IssueTrackingDbContext> dbContextProvider):base(dbContextProvider){}public async Task<List<Issue>> GetInActiveIssueAsynce(){var daysAgo30=DateTime.Now.Subtract(TimeSpan.FromDays(30));var dbSet =await GetDbSetAsync();return await dbSet.Where(i=>//打開狀態!i.IsClosed &&//無分配人i.AssingedUserId ==null &&//創建時間在30天前i.CreationTime < daysAgo30 &&//沒有評論或最后一次評論在30天前(i.LastCommentTime == null || i.LastCommentTime < daysAgo30)).ToListAsync();}} }

在?GetInActiveIssueAsynce?實現方法中,對于未激活的Issue?這條業務規則,需要滿足條件:打開狀態、未分配給任何人、創建超過30天、最近30天沒有評論。

如果我們將業務規則隱含在倉儲中,當我們需要重復使用這個業務邏輯時,問題就出現了。

舉個例子,在 Issue 實體中希望添加一個方法?bool IsInActive(),用于檢測 Issue 是否未激活狀態。

看看如何實現:

public class Issue:AggregateRoot<Guid>,IHasCreationTime {public bool IsClosed {get;private set;}public Guid? AssignedUserId{get;private set;}public DateTime CreationTiem{get;private set;}public DateTime? LastCommentTime{get;private set;}//...public bool IsInActive(){var daysAgo30=DateTime.Now.Subtract(TimeSpan.FromDays(30));return//打開狀態!IsClosed &&//無分配人AssignedUserId ==null &&//創建時間在30天前CreationTime < daysAgo30 &&//無評論或最后一次評論在30天前(LastCommentTime == null || LastCommentTime < daysAgo30 );} }

我們不得不復制、粘貼、修改代碼。如果對未激活的Issue 規則改變了怎么辦?我們應該記得同時更新這兩個地方。這是業務邏輯重復,代碼的壞味道,是相當危險的。

這個問題的一個很好的解決方案就是規約

規約

規約是一個命名的、可重用的可組合的和可測試的類,用于根據業務規則過濾領域對象

ABP框架提供了必要的基礎設施,以輕松創建規約并在你的應用程序代碼中使用。讓我們把?inactive Issue?非活動問題業務規則實現為一個規約類。

using System; using System.Linq.Expressions; using Volo.Abp.Specifications;namespace IssueTracking.Issues {public class InActiveIssueSpecification:Specification<Issue>{public override Expression<Func<Issue,bool>> ToExpression(){var daysAgo30=DateTime.Now.Subtract(TimeSpan.FromDays(30));return i =>//打開狀態!i.IsClosed &&//無分配人i.AssingedUserId ==null &&//創建時間超過30天i.CreationTime < daysAgo30 &&//沒有評論或最后評論超過30天(i.LastCommentTime == null || i.LastCommentTime < daysAgo30)}} }

Specification<T>?基類可以幫助我們簡單地創建規約類,我們可以將倉儲中的表達式移到規約中。

現在,可以在?Issue?實體和?EfCoreIssueRepository?類中使用?InActiveIssueSpecification?規約。

在實體中使用規約

Specification類提供了一個IsSatisfiedBy方法,如果給定的對象(實體)滿足該規范,則返回true。我們可以重新編寫Issue.IsInActive方法,如下所示:

public class Issue:AggregateRoot<Guid>,IHasCreationTime {public bool IsClosed{get;private set;}public Guid? AssignedUserId{get;private set;}public DateTime CreationTiem{get;private set;}public DateTime? LastCommentTime{get;private set;}//...public bool IsInActive(){return new InActiveIssueSpecification().IsSatisfiedBy(this);} }

創建一個?InActiveIssueSpecification?新實例,使用其?IsSatisfiedBy?方法,進行規約驗證。

在倉儲中使用規約

首先,修改倉儲接口:

public interface IIssueRepository:IRepository<Issue,Guid> {Task<List<Issue>> GetIssuesAsync(ISpecification<Issue> spec); }

將方法名?GetInActiveIssuesAsync?改為?GetIssuesAsync?(命名更加簡潔),接收一個規約對象參數。將規約判斷的代碼邏輯從倉儲中移出之后,我們不再需要定義不同的方法來獲取不同條件下的Issue,比如:GetAssignedIssues(...)?獲取已有分配人的問題列表,GetLockedIssues(...)?獲取已鎖定問題列表 等。

修改倉儲的實現:

public class EfCoreIssueRepository:EfCoreRepository<IssueTrackingDbContext,Issue,Guid>,IIssueRepository {public EfCoreIssueRepository(IDbContextProvider<IssueTrackingDbContext> dbContextProvider):base(dbContextProvider){}public async Task<List<Issue>> GetIssuesAsync(ISpecification<Issue> spec){var dbSet = await GetDbSetAsync();return await dbSet.Where(spec.ToExpresion()).ToListAsync();} }

ToExpression()方法返回一個表達式,可以直接作為?Where?方法的參數傳遞,實現實體過濾。

最后,我們將規約實例,傳遞給?GetIssuesAsync?方法:

public class IssueAppServie : ApplciationService,IIssueAppService {private readonly IIssueRepository _issueRepository;public IssueAppService (IIssueRepository issueRepository){_issueRepository = issueRepository;}public async Task DoItAsync(){var issues = await _issueRepository.GetIssuesAsync(new InActiveIssueSpecification(););} }

默認倉儲

實際上,你不需要創建自定義倉儲就能使用規約。標準的IRepository?接口已經擴展?IQueryable?接口,所以你可以直接使用標準的LINQ擴展方法。(非常帥氣!!!)

public class IssueAppServie : ApplciationService,IIssueAppService {private readonly IRepository<Issue,Guid> _issueRepository;public IssueAppService (IRepository<Issue,Guid> issueRepository){_issueRepository = issueRepository;}public async Task DoItAsync(){var queryable = await _issueRepository.GetQueryableAsync();var issues = AsyncExecuter.ToListAsync(queryable.Where(new InActiveIssueSpecification()));} }

AsyncExecuter是ABP框架提供的一個工具類,用于使用異步LINQ擴展方法(比如這里的ToListAsync),而不依賴于EF Core NuGet 包。

組合規約

規范的一個強大的地方是它們是可以組合使用的。假設我們有另一個規約,當問題 Issue 處于指定里程碑中時返回true。

public class MilestoneSpecification : Specification<Issue> {public Guid MilestoneId{get;}public MilestoneSpecification (Guid milestoneId){MilestoneId = milestoneId;}public override Expression<Func<Issue,bool>> ToExpression(){return i => i.MilestoneId == MilestoneId;} }

我們新定義了一個新的參數化規約,和前面定義?InActiveIssueSpecification?不同。那么如何組合兩個規約,獲取指定里程碑中未激活的 Issue(問題)呢?

public class IssueAppServie : ApplciationService,IIssueAppService {private readonly IRepository<Issue,Guid> _issueRepository;public IssueAppService (IRepository<Issue,Guid> issueRepository){_issueRepository = issueRepository;}public async Task DoItAsync(Guid milesoneId){var queryable = await _issueRepository.GetQueryableAsync();var issues = AsyncExecuter.ToListAsync(queryable.Where(new InActiveIssueSpecification().Add(new MilestoneSpecification(milestoneId)).ToExpression()));} }

示例中使用?Add?擴展方法組合規約,還有更多的擴展方法,比如:Or(...)?AndNot(...)。

學習幫助

圍繞DDDABP Framework兩個核心技術,后面還會陸續發布核心構件實現、綜合案例實現系列文章,敬請關注!

ABP Framework 研習社(QQ群:726299208)?專注 ABP Framework 學習及DDD實施經驗分享;示例源碼、電子書共享,歡迎加入!

總結

以上是生活随笔為你收集整理的基于ABP落地领域驱动设计-03.仓储和规约最佳实践和原则的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。