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

歡迎訪問 生活随笔!

生活随笔

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

编程问答

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

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

dotNET兄弟會?

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

公眾號 ?

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

系列文章

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

倉儲

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

倉儲的通用原則

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

倉儲中不包含領域邏輯

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

示例:從倉儲中獲取?inactive?狀態(tài)的 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;} }

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

為了更好地理解,我們繼續(xù)看看接口方法的實現(xiàn):

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=>//打開狀態(tài)!i.IsClosed &&//無分配人i.AssingedUserId ==null &&//創(chuàng)建時間在30天前i.CreationTime < daysAgo30 &&//沒有評論或最后一次評論在30天前(i.LastCommentTime == null || i.LastCommentTime < daysAgo30)).ToListAsync();}} }

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

如果我們將業(yè)務規(guī)則隱含在倉儲中,當我們需要重復使用這個業(yè)務邏輯時,問題就出現(xiàn)了。

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

看看如何實現(xiàn):

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//打開狀態(tài)!IsClosed &&//無分配人AssignedUserId ==null &&//創(chuàng)建時間在30天前CreationTime < daysAgo30 &&//無評論或最后一次評論在30天前(LastCommentTime == null || LastCommentTime < daysAgo30 );} }

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

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

規(guī)約

規(guī)約是一個命名的、可重用的可組合的和可測試的類,用于根據(jù)業(yè)務規(guī)則過濾領域對象

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

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 =>//打開狀態(tài)!i.IsClosed &&//無分配人i.AssingedUserId ==null &&//創(chuàng)建時間超過30天i.CreationTime < daysAgo30 &&//沒有評論或最后評論超過30天(i.LastCommentTime == null || i.LastCommentTime < daysAgo30)}} }

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

現(xiàn)在,可以在?Issue?實體和?EfCoreIssueRepository?類中使用?InActiveIssueSpecification?規(guī)約。

在實體中使用規(guī)約

Specification類提供了一個IsSatisfiedBy方法,如果給定的對象(實體)滿足該規(guī)范,則返回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);} }

創(chuàng)建一個?InActiveIssueSpecification?新實例,使用其?IsSatisfiedBy?方法,進行規(guī)約驗證。

在倉儲中使用規(guī)約

首先,修改倉儲接口:

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

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

修改倉儲的實現(xiàn):

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?方法的參數(shù)傳遞,實現(xiàn)實體過濾。

最后,我們將規(guī)約實例,傳遞給?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(););} }

默認倉儲

實際上,你不需要創(chuàng)建自定義倉儲就能使用規(guī)約。標準的IRepository?接口已經(jīng)擴展?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 包

組合規(guī)約

規(guī)范的一個強大的地方是它們是可以組合使用的。假設我們有另一個規(guī)約,當問題 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;} }

我們新定義了一個新的參數(shù)化規(guī)約,和前面定義?InActiveIssueSpecification?不同。那么如何組合兩個規(guī)約,獲取指定里程碑中未激活的 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?擴展方法組合規(guī)約,還有更多的擴展方法,比如:Or(...)?AndNot(...)。

學習幫助

圍繞DDDABP Framework兩個核心技術,后面還會陸續(xù)發(fā)布核心構件實現(xiàn)綜合案例實現(xiàn)系列文章,敬請關注!

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

總結

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

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