ABP vNext 针对于应用服务层,为我们单独设计了一个模块进行实现,即 Volo.Abp.Ddd.Application 模块。
PS:最近博主也是在恶补 DDD 相关的知识,这里推荐大家看一下 ThoughtWorks 的 DDD 相关文章。
关于 DDD 相关的著作,我这儿还是推荐经典的那三本《领域驱动设计:软件核心复杂性应对之道》、《实现领域驱动设计》、《领域驱动设计精粹》。
DDD 的学习整体来说是比较枯燥的,而且偏理论化的知识。所以需要结合大量实例来看,反复对照书中的概念加深理解。不仅要看别人的实例,自己也要尝试运用 DDD 的战略方法和战术方法进行设计。
应用服务层在 DDD 分层架构里面是最顶层的,一般与前端(展示层)打交道的都是应用服务层。常规的开发人员,如果没有遵循 DDD 理论来进行开发的话,应用服务层是十分臃肿的,里面全是业务逻辑。而领域层里面则是空无一物,全是贫血的领域模型对象。这种模式被称之为 贫血领域模型模式,这是一个 反模式。
这里我就不再赘述应用服务层与 DDD 之间的关系了,在这里你可以看作它是一个 API 接口实现类,你所有对外开放的接口都是通过应用服务层暴露的,接口的方法应该与用例相对应。
二、源码分析应用服务层模块里面比较简单,只有两个文件夹,分别存放了数据传输模型(Dtos)和应用服务基类定义(Services)。
2.1 启动模块首先我们还是按照之前的顺序,看一个模块先看他的模块类。这里我们先看一下 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 判断哪些类型不被忽略 (即只会自动注册实现了三种接口的类型成为控制器)。
2.2 应用服务基类ABP vNext 提供了标准基类 ApplicationService 和简单 Crud 基类 CrudAppService 给我们使用,前者只是继承了 IApplicationService 接口,并提供了基本组件的简单基类。而后者则是定义了 Crud 操作所需要的所有 API 方法,你只需要继承这个基类对象,填充相应的泛型参数,就可以快速实现一个 Crud 接口。
2.2.1 简单基类简单基类里面我们首先需要注意的是它实现的接口,你可以发现 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; }