从架构角度来讲,ApplicationService究竟应该如何定位,一种说法是直接对应用例UseCase, 也就是直接对应UI, 这个UI是广义的,不仅仅是浏览器的页面,也包括API调用。还是从我曾经踩过的一个坑说起吧:

 public class ProductImportService
        : AdvancedAsyncCrudAppService<Product, ProductDto, PagedResultRequestDto>
        ,
        IProductImportService
    {
        ......public ProductImportService(......
                                )
        : base(productRepository)
        {
            ......
        }

        //[MyIgnoreApiAttribute]
        //[DisableValidation]
        //[DisableAuditing]

        private void SaveProductAndTestSizeHead( RawData rawData )
        {
           ......
        }

        //[DisableValidation]
        //[DisableAuditing]
        private void SaveStandardSizeValue( RawData rawData)
        {
            ......
        }

        //[DisableValidation]
        //[DisableAuditing]
        private void SaveTestSizeInfo( RawData rawData)
        {
            ......
        }

        private void SaveTestSizeValue( RawData rawData)
        {
            List<TestSizeValue> newTestSizeValues = rawData.TestSizeValues;

            var firstEntity = newTestSizeValues.FirstOrDefault();
            if (firstEntity == null)
            {
                return;
            }

            var dbExistEntities = _testSizeValueValueRepository.GetAllIncluding( os => os.TestSizeInfo
                                                                               , os => os.TestSizeInfo.TestSizeHead
                                                                               , os => os.StandardSizeValue
                                                                               )
                          .Where(os => os.TestSizeInfo.TestSizeHead.Id == rawData.TestSizeHead.Id
                                       && os.IsAim == firstEntity.IsAim
                                 ).ToList();

            FilterValues(newTestSizeValues, dbExistEntities, out List<TestSizeValue> newEnities, out List<TestSizeValue> updateEnities);

            foreach( var entity in newEnities)
            {
                entity.TestSizeInfo = rawData.TestSizeInfos.FirstOrDefault(tsi => tsi.IsSame(entity.TestSizeInfo));
                entity.StandardSizeValue = rawData.StandardSizeValues.FirstOrDefault(ssv => ssv.IsSame(entity.StandardSizeValue));
            }
            _testSizeValueValueRepository.BulkInsert(newEnities);  // 批量插入新的

            foreach( var updateEntity in updateEnities)
            {
                _testSizeValueValueRepository.Update(updateEntity); // 修改已存在的
            }

            rawData.TestSizeValues = newTestSizeValues;
        }

        private void FilterValues(List<TestSizeValue> testSizeValues, List<TestSizeValue> dbExistEntities, out List<TestSizeValue> newEnities, out List<TestSizeValue> updateEnities)
        {
           ......
        }

        public RawData Save(RawData rawData)
        {
            SaveProductAndTestSizeHead( rawData);
            SaveStandardSizeValue( rawData);
            SaveTestSizeInfo( rawData);
            SaveTestSizeValue( rawData);

            return rawData;
        }

这是一个从Excel文件中导入数据的场景,每个文件的数据是个矩阵,有50多列,有30多行,数据有50x30=1500个左右,导入场景性能是个关键因素,因为它决定了单位时间内能处理多少个Excel文件,调试时发现每个文件的处理时间是90秒左右,首先想到的方案是改用批量插入,改善到10秒左右,再也没法改善了。于是在各个地方加了时间计算,终于发现问题出在哪里了,其实瓶颈并不在数据库操作,而是在方法执行前,也就是ABP拦截器里消耗的时间,这个拦截器就是Audit Logging : User, browser, IP address, calling service, method, parameters, calling time, execution duration and some other informations are automatically saved for each request based on conventions and configurations. 审计全记录,最耗时的是记录 parameters,每次记录都要序列化(用的是Json),如果是大数据库的化,这块是非常非常耗时的!后来仔细研究ABP源码,其实很简单

public sealed class AbpKernelModule : AbpModule
    {
        public override void PreInitialize()
        {
            IocManager.AddConventionalRegistrar(new BasicConventionalRegistrar());

            IocManager.Register<IScopedIocResolver, ScopedIocResolver>(DependencyLifeStyle.Transient);

            ValidationInterceptorRegistrar.Initialize(IocManager);
            AuditingInterceptorRegistrar.Initialize(IocManager);
            UnitOfWorkRegistrar.Initialize(IocManager);
            AuthorizationInterceptorRegistrar.Initialize(IocManager);

            Configuration.Auditing.Selectors.Add(
                new NamedTypeSelector(
                    "Abp.ApplicationServices",
                    type => typeof(IApplicationService).IsAssignableFrom(type)
                    )
                );

           ......
        }

       ......
    }

ABP是通过拦截器的方式,注入了代码(功能),ValidationInterceptor 验证拦截器、AuditingInterceptor 审计拦截器、AuthorizationInterceptor 认证拦截器,AuditingInterceptor 审计拦截器会拦截所有ApplicationServices

Configuration.Auditing.Selectors.Add(
                new NamedTypeSelector(
                    "Abp.ApplicationServices",
                    type => typeof(IApplicationService).IsAssignableFrom(type)
                    )
                );

AuditingInterceptor 审计拦截器,有与其配套的Attribute,来实现申明式Enable/Disable

namespace Abp.Auditing
{
    internal class AuditingInterceptor : IInterceptor
    {
       ......public void Intercept(IInvocation invocation)
        {
            if (AbpCrossCuttingConcerns.IsApplied(invocation.InvocationTarget, AbpCrossCuttingConcerns.Auditing))
            {
                invocation.Proceed();
                return;
            }

            if (!_auditingHelper.ShouldSaveAudit(invocation.MethodInvocationTarget))
            {
                invocation.Proceed();
                return;
            }

            var auditInfo = _auditingHelper.CreateAuditInfo(invocation.MethodInvocationTarget, invocation.Arguments);

            if (AsyncHelper.IsAsyncMethod(invocation.Method))
            {
                PerformAsyncAuditing(invocation, auditInfo);
            }
            else
            {
                PerformSyncAuditing(invocation, auditInfo);
            }
        }

        ......
    }
}
public class AuditingHelper : IAuditingHelper, ITransientDependency
    {
        ......public bool ShouldSaveAudit(MethodInfo methodInfo, bool defaultValue = false)
        {
            if (!_configuration.IsEnabled)
            {
                return false;
            }

            if (!_configuration.IsEnabledForAnonymousUsers && (AbpSession?.UserId == null))
            {
                return false;
            }

            if (methodInfo == null)
            {
                return false;
            }

            if (!methodInfo.IsPublic)
            {
                return false;
            }

            if (methodInfo.IsDefined(typeof(AuditedAttribute), true))
            {
                return true;
            }

            if (methodInfo.IsDefined(typeof(DisableAuditingAttribute), true))
            {
                return false;
            }

            var classType = methodInfo.DeclaringType;
            if (classType != null)
            {
                if (classType.IsDefined(typeof(AuditedAttribute), true))
                {
                    return true;
                }

                if (classType.IsDefined(typeof(DisableAuditingAttribute), true))
                {
                    return false;
                }

                if (_configuration.Selectors.Any(selector => selector.Predicate(classType)))
                {
                    return true;
                }
            }

            return defaultValue;
        }

所以我的,第一个解决方案是:

[DisableValidation]
[DisableAuditing]

但经过仔细分析,其实在导入这个场景中,Save保存数据到DB, 其实不是UseCase用例,而Import才是UseCase, Save只是Import的一个步骤; Import 的第一步是Parse解析Excel文件,第二步才是Save; 因此Save不应该作为ApplicationService(比较重的服务,ABP会自动注入很多关切),上策应该把Save作为DomainServie(轻量级服务,ABP不会自动注入很多东西),以下是Excel导入Save的最终解决方案:

public class ProductImportService
        :DomainService
        ,
        IProductImportService
    {
        ......
        public ProductImportService(......
                                )
        : base(productRepository)
        {
            ......
        }

        //[MyIgnoreApiAttribute]
        //[DisableValidation]
        //[DisableAuditing]

        private void SaveProductAndTestSizeHead( RawData rawData )
        {
           ......
        }

        //[DisableValidation]
        //[DisableAuditing]
        private void SaveStandardSizeValue( RawData rawData)
        {
            ......
        }

        //[DisableValidation]
        //[DisableAuditing]
        private void SaveTestSizeInfo( RawData rawData)
        {
            ......
        }

        private void SaveTestSizeValue( RawData rawData)
        {
           ......
        }

        private void FilterValues(List<TestSizeValue> testSizeValues, List<TestSizeValue> dbExistEntities, out List<TestSizeValue> newEnities, out List<TestSizeValue> updateEnities)
        {
           ......
        }

        public RawData Save(RawData rawData)
        {
            SaveProductAndTestSizeHead( rawData);
            SaveStandardSizeValue( rawData);
            SaveTestSizeInfo( rawData);
            SaveTestSizeValue( rawData);

            return rawData;
        }

总结,ApplicationService 很强大,但也要合适的使用,分清ApplicationService和DomainServie的适合场景,也许是ABP或DDD的一个重要的架构选择!

使用ABP框架踩过的坑系列3的更多相关文章

  1. ABP框架踩过的坑系列6

    ABP框架踩过的坑系列6 应是无事.齐侯方才的确到了吴纠庭院https://www.mixcloud.com/ltTFvU888smi8jS/几日行军劳顿其实齐侯本应该睡下了https://www.m ...

  2. 使用ABP框架踩过的坑系列1

        企业级(例如ERP)应用, 一遍一遍的在重复:认证.验证.异常处理.日志.国际化和本地化.数据库连接管理.配置管理. 审计记录等,同时.NET有很多最佳实践:分层.模块化.DDD领域驱动.DI ...

  3. 使用ABP框架踩过的坑系列4

    数据库连接和事务管理,是数据库应用中的最重要概念之一.做过的人,都会头疼:何时Open一个连接?何时Start一个事务?何时Dispose这个连接?... ABP框架试图用一个叫做UnitOfWork ...

  4. 使用ABP框架踩过的坑系列5

    DDD领域驱动开发,实际是为复杂的业务场景而生的,为了让开发人员专注于业务,而操作系统.数据库.网络之类的技术细节,必须要持久透明化:实际就是数据库系统DBMS的ORM抽象,目标就是业务不需要考虑数据 ...

  5. 使用ABP框架踩过的坑系列2

    ABP中有很多惯例,如果使用得当,可以事半功倍,如果使用不当,也会有很大的麻烦,是否适当其实还是要看Need需求 ASP.NET Boilerplate (ABP) is an open source ...

  6. ABP框架踩坑记录

    ABP框架踩坑记录 ASP.NET Boilerplate是一个专用于现代Web应用程序的通用应用程序框架. 它使用了你已经熟悉的工具,并根据它们实现最佳实践. 文章目录 使用MySQL 配置User ...

  7. Abp框架之执行Update-Database 命令系列错误

    废话不多说,直接开门见山.首先的 第一个错误:一般都是,碰到这个问题不要慌,先不要急着去查看sql服务是否开启,首先按F5启动项目,报错之后直接终止项目,然后再执行Update-Database命令 ...

  8. 谈谈出入React框架踩过的坑

    1 在JSX的元素中写入内联样式,例如<div style={"color:blue"}></div> 报错:warning:Style prop valu ...

  9. 踩过的坑系列之InputStream.read(byte[])方法

    项目之前都是好好的,最近现场那边出现一个问题,报错不是合法的json字符串,这个json字符串是通过http请求访问获得的. 通过直接在浏览器上直接访问http这个请求,发现返回的json也是完全正确 ...

随机推荐

  1. poj1015 正解--二维DP(完全背包)

    题目链接:http://poj.org/problem?id=1015 错误解法: 网上很多解法是错误的,用dp[i][j]表示选择i个人差值为j的最优解,用path[i][j]存储路径,循环次序为“ ...

  2. MD5摘要算法实现

    网上找到的实现md5函数代码,包括一个头文件md5.h和一个源文件md5.c,用下面的测试代码test.c测试通过,各文件依次如下: 头文件md5.h: #ifndef MD5_H #define M ...

  3. 【英宝通Unity4.0公开课学习 】(四)GUI到物理引擎

    今天老妈打电话来说和老爸吵架了... 真的是家家都有本难念的经啊.前后帮她分析了个半小时才帮她解开心结...现在想想老爸还是蛮可怜的,连分享的人都木有 讲的GUI都看睡着了...因为想着可以用NGUI ...

  4. Oracle的下载、安装和配置

    win10下Oracle的下载.安装和配置     --------------siwuxie095                 1.首先到Oracle官网下载Oracle,中文版官网,传送阵:点 ...

  5. swift textfield 和 textview 实时获取 输入内容

    textfield : func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replac ...

  6. Macbook pro睡眠状态恢复后没声音的解决办法

    杀招: sudo killall coreaudiod macos会自动重启进程,恢复声音

  7. WinScp获取一个文件

    CD /d C:\Program Files (x86)\WinSCPWinSCP.exe /console /command "option batch continue" &q ...

  8. [SoapUI] 设置最大等待时间,不断重复的去发送一个request,每次从response中获取一个status,直到这个status从一种状态变成另外一种状态

    import com.eviware.soapui.support.GroovyUtils def groovyUtils = new GroovyUtils( context ) def holde ...

  9. Jmeter运行过程中如何让Fiddler同时可以抓获到服务器的应答报文

    在默认情况下,Jmeter运行过程中,Fiddler是抓不到对应的应答报文的. 但是,在某些时候,我们希望分析Jmeter执行失败的原因,想了解Jmeter获取到的应答报文是否有问题,就需要同服务器返 ...

  10. java.text.SimpleDateFormat的使用

    SimpleDateFormat 是一个以国别敏感的方式格式化和分析数据的具体类. 它允许格式化 (date -> text).语法分析 (text -> date)和标准化. Simpl ...