[Abp vNext 源码分析] - 6. DDD 的应用层支持 (应用服务)

1、简要介绍

ABP vNext 针对于应用服务层,为咱们单独设计了一个模块进行实现,即 Volo.Abp.Ddd.Application 模块。html

PS:最近博主也是在恶补 DDD 相关的知识,这里推荐你们看一下 ThoughtWorks 的 DDD 相关文章。前端

关于 DDD 相关的著做,我这儿仍是推荐经典的那三本《领域驱动设计:软件核心复杂性应对之道》、《实现领域驱动设计》、《领域驱动设计精粹》架构

DDD 的学习总体来讲是比较枯燥的,并且偏理论化的知识。因此须要结合大量实例来看,反复对照书中的概念加深理解。不只要看别人的实例,本身也要尝试运用 DDD 的战略方法和战术方法进行设计。app

应用服务层在 DDD 分层架构里面是最顶层的,通常与前端(展现层)打交道的都是应用服务层。常规的开发人员,若是没有遵循 DDD 理论来进行开发的话,应用服务层是十分臃肿的,里面全是业务逻辑。而领域层里面则是空无一物,全是贫血的领域模型对象。这种模式被称之为 贫血领域模型模式,这是一个 反模式框架

这里我就再也不赘述应用服务层与 DDD 之间的关系了,在这里你能够看做它是一个 API 接口实现类,你全部对外开放的接口都是经过应用服务层暴露的,接口的方法应该与用例相对应。async

2、源码分析

应用服务层模块里面比较简单,只有两个文件夹,分别存放了数据传输模型(Dtos)和应用服务基类定义(Services)。ide

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 ,往里面添加了 IRemoteServiceIApplicationServiceIUnitOfWOrkEnabled 三种接口类型。添加了三种类型以后,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;
}

剩余的 IValidationEnabledIUnitOfWorkEnabledIAuditingEnabledITransientDependency 接口相似于一个启用标识,只要类型继承了该接口,就会执行一些特殊的操做。

回到以前的简单基类里面,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 异常。

2.2.2 Crud 基类

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 修饰的,你也能够随时重写来达到本身的目的。

2.3 数据传输对象

通常来讲,应用服务层返回给展现层的数据确定是某个实体对象的部分属性,或者是多个聚合的总体,这个时候就须要 DTO 来帮咱们处理应用服务层与外部的数据交换了。

ABP vNext 在应用服务模块定义了经常使用的一些 DTO 对象,例如实体 DTO 和分页查询 DTO,关于这些 DTO 你只需将其看做一个数据容器便可,不须要太多关注,这里也没有太多要讲的。

3、总结

ABP vNext 提供的应用服务层模块仍是比较简单的,里面主要是针对应用服务基类进行了预约义。方便咱们开发人员进行业务开发,而不须要本身实现这些繁杂的基类。

在 DDD 当中,应用服务是表达 用户用例用户故事 的主要手段,应用服务只是经过领域对象/领域服务来表达需求用例的一个组件。不要将业务逻辑泄漏到应用服务当中,这种设计最终会致使贫血领域模型。

4、点击我跳转到文章目录

相关文章
相关标签/搜索