使用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的一个重要的架构选择!
使用ABP框架踩过的坑系列3的更多相关文章
- ABP框架踩过的坑系列6
ABP框架踩过的坑系列6 应是无事.齐侯方才的确到了吴纠庭院https://www.mixcloud.com/ltTFvU888smi8jS/几日行军劳顿其实齐侯本应该睡下了https://www.m ...
- 使用ABP框架踩过的坑系列1
企业级(例如ERP)应用, 一遍一遍的在重复:认证.验证.异常处理.日志.国际化和本地化.数据库连接管理.配置管理. 审计记录等,同时.NET有很多最佳实践:分层.模块化.DDD领域驱动.DI ...
- 使用ABP框架踩过的坑系列4
数据库连接和事务管理,是数据库应用中的最重要概念之一.做过的人,都会头疼:何时Open一个连接?何时Start一个事务?何时Dispose这个连接?... ABP框架试图用一个叫做UnitOfWork ...
- 使用ABP框架踩过的坑系列5
DDD领域驱动开发,实际是为复杂的业务场景而生的,为了让开发人员专注于业务,而操作系统.数据库.网络之类的技术细节,必须要持久透明化:实际就是数据库系统DBMS的ORM抽象,目标就是业务不需要考虑数据 ...
- 使用ABP框架踩过的坑系列2
ABP中有很多惯例,如果使用得当,可以事半功倍,如果使用不当,也会有很大的麻烦,是否适当其实还是要看Need需求 ASP.NET Boilerplate (ABP) is an open source ...
- ABP框架踩坑记录
ABP框架踩坑记录 ASP.NET Boilerplate是一个专用于现代Web应用程序的通用应用程序框架. 它使用了你已经熟悉的工具,并根据它们实现最佳实践. 文章目录 使用MySQL 配置User ...
- Abp框架之执行Update-Database 命令系列错误
废话不多说,直接开门见山.首先的 第一个错误:一般都是,碰到这个问题不要慌,先不要急着去查看sql服务是否开启,首先按F5启动项目,报错之后直接终止项目,然后再执行Update-Database命令 ...
- 谈谈出入React框架踩过的坑
1 在JSX的元素中写入内联样式,例如<div style={"color:blue"}></div> 报错:warning:Style prop valu ...
- 踩过的坑系列之InputStream.read(byte[])方法
项目之前都是好好的,最近现场那边出现一个问题,报错不是合法的json字符串,这个json字符串是通过http请求访问获得的. 通过直接在浏览器上直接访问http这个请求,发现返回的json也是完全正确 ...
随机推荐
- luoguP1064 金明的预算方案 (有依赖的背包问题)
题目链接:https://www.luogu.org/problemnew/show/P1064 这是一个有依赖的背包问题,属于01背包的变式.这题还好,每个主件最多有2个附件,那么在对主件进行背包的 ...
- INI
.ini 文件是Initialization File的缩写,即初始化文件,是windows的系统配置文件所采用的存储格式,统管windows的各项配置,一般用户就用windows提供的各项图形化管理 ...
- Linux 下 FastDFS v5.08 分布式文件系统的安装
一.系统安装目录 源代码包目录 /data/wwwroot libevent安装目录 /usr/local/libevent FastDFS安装目录 /data/fastdfs nginx安装目录 / ...
- MVC4升级到MVC5未能加载文件或程序集System.Web.WebPages.Razor, Version=3.0.0.0
首先,我并没有升级他,头一天还是好好的,用的都是2.0.0.0版本的,今天来打开就出现了这个错误: 未能加载文件或程序集“System.Web.WebPages.Razor, Version=3.0. ...
- DB2 SQL1477N问题
ERROR [55019] [IBM][DB2/NT] SQL1477N For table "DB_YHJX.YHJX_FHDKFHZ" an object "521 ...
- DB2锁机制
相比较Oracle来说,DB2的锁机制麻烦了很多,而且这个麻烦带来的不是性能的上升而是下降,不过如果细致了解的话,只能感慨不愧是数据库理论诞生的公司,在实现数据库理论上比Oracle全面得多. ...
- Java中弹出对话框中的几种方式
1.显示一个错误对话框,该对话框显示的 message 为 'alert': JOptionPane.showMessageDialog(null, "alert", " ...
- Laravel图表扩展包推荐:Charts
2016年11月15日 · 2283次 · 4条 · laravel,package,charts 介绍 在项目开发中,创建图表通常是一件痛苦的事情.因为你必须将数据转换为图表库支持的格式传输 ...
- Ckeditor上传图片返回的JS直接显示出来,未执行!!!
Ckeditor上传图片网上有很多教程. 下面是我今天下午遇到的一个坑...自己挖的坑. 在conotroller里 我开始习惯性的 response.setContentType("app ...
- 类里面的非static const成员
类里面的成员变量可以用const修饰,在只用const不用static修饰的情况下,这种使用的限制比较多 (1)不能定义处初始化,必须在类的构造函数初始化列表里面初始化(虽然在vs中,可以在定义处初始 ...