使用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的一個重要的架構選擇!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM