ABP vNext 針對於應用服務層,爲咱們單獨設計了一個模塊進行實現,即 Volo.Abp.Ddd.Application 模塊。html
PS:最近博主也是在惡補 DDD 相關的知識,這裏推薦你們看一下 ThoughtWorks 的 DDD 相關文章。前端
關於 DDD 相關的著做,我這兒仍是推薦經典的那三本《領域驅動設計:軟件核心複雜性應對之道》、《實現領域驅動設計》、《領域驅動設計精粹》。架構
DDD 的學習總體來講是比較枯燥的,並且偏理論化的知識。因此須要結合大量實例來看,反覆對照書中的概念加深理解。不只要看別人的實例,本身也要嘗試運用 DDD 的戰略方法和戰術方法進行設計。app
應用服務層在 DDD 分層架構裏面是最頂層的,通常與前端(展現層)打交道的都是應用服務層。常規的開發人員,若是沒有遵循 DDD 理論來進行開發的話,應用服務層是十分臃腫的,裏面全是業務邏輯。而領域層裏面則是空無一物,全是貧血的領域模型對象。這種模式被稱之爲 貧血領域模型模式,這是一個 反模式。框架
這裏我就再也不贅述應用服務層與 DDD 之間的關係了,在這裏你能夠看做它是一個 API 接口實現類,你全部對外開放的接口都是經過應用服務層暴露的,接口的方法應該與用例相對應。async
應用服務層模塊裏面比較簡單,只有兩個文件夾,分別存放了數據傳輸模型(Dtos)和應用服務基類定義(Services)。ide
首先咱們仍是按照以前的順序,看一個模塊先看他的模塊類。這裏咱們先看一下 AbpDddApplicationModule
的代碼。源碼分析
[DependsOn( typeof(AbpDddDomainModule), typeof(AbpSecurityModule), typeof(AbpObjectMappingModule), typeof(AbpValidationModule), typeof(AbpAuthorizationModule), typeof(AbpHttpAbstractionsModule), typeof(AbpSettingsModule), typeof(AbpFeaturesModule) )] // 不要看上面依賴這麼多模塊,主要是由於基類會用到不少基礎組件。 public class AbpDddApplicationModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) { // 配置接口類型。 Configure<ApiDescriptionModelOptions>(options => { options.IgnoredInterfaces.AddIfNotContains(typeof(IRemoteService)); options.IgnoredInterfaces.AddIfNotContains(typeof(IApplicationService)); options.IgnoredInterfaces.AddIfNotContains(typeof(IUnitOfWorkEnabled)); }); } }
能夠看到,在上述代碼裏面,只作了一件事情,就是調用 ApiDescriptionModelOptions
,往裏面添加了 IRemoteService
、IApplicationService
、IUnitOfWOrkEnabled
三種接口類型。添加了三種類型以後,ABP vNext 根據應用服務類建立控制器時,就會從這個 IgnoredInterfaces
判斷哪些類型不被忽略 (即只會自動註冊實現了三種接口的類型成爲控制器)。學習
ABP vNext 提供了標準基類 ApplicationService
和簡單 Crud 基類 CrudAppService
給咱們使用,前者只是繼承了 IApplicationService
接口,並提供了基本組件的簡單基類。然後者則是定義了 Crud 操做所須要的全部 API 方法,你只須要繼承這個基類對象,填充相應的泛型參數,就能夠快速實現一個 Crud 接口。設計
簡單基類裏面咱們首先須要注意的是它實現的接口,你能夠發現 ApplicationService
實現了諸多接口,不過這些接口更多的是相似於標識接口。
public abstract class ApplicationService : IApplicationService, IAvoidDuplicateCrossCuttingConcerns, IValidationEnabled, IUnitOfWorkEnabled, IAuditingEnabled, ITransientDependency { // ... 其餘代碼 }
全部應用服務都必須繼承 IApplicationService
,這個是確定的,否則 ABP vNext 不會爲咱們生成須要的控制器。
其次是 IAvoidDuplicateCrossCuttingConcerns
接口,這個接口最先能夠追溯到老版本 ABP 框架裏面。它的主要做用是防止攔截器進行重複執行。
public interface IAvoidDuplicateCrossCuttingConcerns { List<string> AppliedCrossCuttingConcerns { get; } }
例如調用購買這個 API 接口,首先會進入 ASP.NET Core 的審計日誌 Filter,在 Filter 裏面會將這個 API 接口歸屬的類型的 List
容器(接口裏面定義的 List )裏面寫入一條記錄,說明已經經過審計日誌過濾器記錄了。
寫了審計日誌以後,又會進入審計日誌攔截器,這個時候攔截器就會對指定的類型進行判斷,看是否已經被執行過了,由於這個類型的 List
容器有了以前過濾器的記錄,因此不會重複執行。
public override void Intercept(IAbpMethodInvocation invocation) { if (!ShouldIntercept(invocation, out var auditLog, out var auditLogAction)) { invocation.Proceed(); return; } // ... 審計日誌記錄。 } protected virtual bool ShouldIntercept( IAbpMethodInvocation invocation, out AuditLogInfo auditLog, out AuditLogActionInfo auditLogAction) { // 判斷實例的 List 容器裏面,是否寫入了 AbpCrossCuttingConcerns.Auditing。 if (AbpCrossCuttingConcerns.IsApplied(invocation.TargetObject, AbpCrossCuttingConcerns.Auditing)) { return false; } // ... 其餘代碼 return true; }
剩餘的 IValidationEnabled
、IUnitOfWorkEnabled
、IAuditingEnabled
、ITransientDependency
接口相似於一個啓用標識,只要類型繼承了該接口,就會執行一些特殊的操做。
回到以前的簡單基類裏面,ABP vNext 爲咱們注入了大量基礎設施,例如獲取當前用戶的 ICurrentUser
組件,獲取當前租戶的 ICurrentTenant
組件,還有日誌組件等。
除了基礎組件,ABP vNext 在簡單基類裏面還提供了一個權限檢測方法,用戶檢測當前用戶是否具有某些權限。
protected virtual async Task CheckPolicyAsync([CanBeNull] string policyName) { if (string.IsNullOrEmpty(policyName)) { return; } await AuthorizationService.CheckAsync(policyName); }
在不具有權限的時候,ABP vNext 會拋出 AbpAuthorizationException
異常。
Crud 基類能夠極大減小對於某些簡單對象的代碼編寫,例如我有個客戶管理接口,只須要簡單地增刪改查操做。那麼我就能夠直接繼承自 Crud 基類,給它填寫和是的泛型參數以後,ABP vNext 就會爲咱們生成帶有增刪改查操做的應用服務對象。
這個 Crud 基類擁有多個泛型定義與實現,除了真正的實現之外,其餘的都是簡單的調用基類方法而已。咱們直接進入主題,看一下類型簽名爲 public abstract class CrudAppService<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
的基類。
public abstract class CrudAppService<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput> : ApplicationService, ICrudAppService<TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput> where TEntity : class, IEntity<TKey> where TGetOutputDto : IEntityDto<TKey> where TGetListOutputDto : IEntityDto<TKey> { public virtual async Task<TGetOutputDto> GetAsync(TKey id) { // 具體代碼。 } public virtual async Task<PagedResultDto<TGetListOutputDto>> GetListAsync(TGetListInput input) { // 具體代碼。 } public virtual async Task<TGetOutputDto> CreateAsync(TCreateInput input) { // 具體代碼。 } public virtual async Task<TGetOutputDto> UpdateAsync(TKey id, TUpdateInput input) { // 具體代碼。 } public virtual async Task DeleteAsync(TKey id) { // 具體代碼。 } }
從上述代碼能夠看到基類根據傳入的泛型參數,將會爲咱們實現常規的增刪改查邏輯。咱們也能夠隨時重寫這些方法,來達到一些個性化的操做。
ABP vNext 抽象了公用接口之外,在內部還編寫了諸如 MapToEntity()
和 MapToEntity()
等內部共用方法,這裏就再也不詳細贅述,這些方法都是 protected
修飾的,你也能夠隨時重寫來達到本身的目的。
通常來講,應用服務層返回給展現層的數據確定是某個實體對象的部分屬性,或者是多個聚合的總體,這個時候就須要 DTO 來幫咱們處理應用服務層與外部的數據交換了。
ABP vNext 在應用服務模塊定義了經常使用的一些 DTO 對象,例如實體 DTO 和分頁查詢 DTO,關於這些 DTO 你只需將其看做一個數據容器便可,不須要太多關注,這裏也沒有太多要講的。
ABP vNext 提供的應用服務層模塊仍是比較簡單的,裏面主要是針對應用服務基類進行了預約義。方便咱們開發人員進行業務開發,而不須要本身實現這些繁雜的基類。
在 DDD 當中,應用服務是表達 用戶用例 和 用戶故事 的主要手段,應用服務只是經過領域對象/領域服務來表達需求用例的一個組件。不要將業務邏輯泄漏到應用服務當中,這種設計最終會致使貧血領域模型。