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

匿名 (未验证) 提交于 2019-12-02 22:10:10

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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!