zoukankan      html  css  js  c++  java
  • 使用ABP框架踩过的坑系列3

    从架构角度来讲,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的一个重要的架构选择!

  • 相关阅读:
    ssh scp命令详解
    python模块与包
    python参数Sample Code
    python 多线程简介
    python virtualenv环境安装(ubuntu)
    python pip 代理设置
    mysql资源总结
    mysql索引原理及用法
    Oracle学习笔记:利用user_segments查表的大小
    Oracle学习笔记:dba_tables、all_tables、user_tables区别
  • 原文地址:https://www.cnblogs.com/szdlsoft/p/9171758.html
Copyright © 2011-2022 走看看