1.2、ABP入门
- XXX.Application(应用层):
进行展现层与领域层之间的协调,协调业务对象来执行特定的应用程序的任务。它不包含业务逻辑,主要包含一些模型、abp重要的数据传输DTO,包括数据库映射实体,前端视图模型转实体(Entity)对象。
一个应用服务方法通常被认为是一个工作单元(Unit of Work),使用一种像AutoMapper这样的工具来进行实体与DTO之间的映射,前端参数传入有限性验证等等。
- XXX.Core(领域层):
领域层就是业务层,是一个项目的核心,所有业务规则都应该在领域层实现。包括业务对象和业务规则,这是应用程序的核心层。
实体(Entity):
实体代表业务领域的数据和操作,在实践中,通过用来映射成数据库表。
ABP中所有的实体类都继承自Entity,而Entity实现了IEntity接口;而IEntity接口是一个泛型接口,通过泛型指定主键Id类型,默认的Entity的主键类型是int类型。
仓储接口(IRepository):
仓储用来操作数据库进行数据存取。仓储接口在领域层定义,而仓储的实现类应该写在基础设施层。 ABP针对不同的ORM框架对这个接口进行了默认的实现:
1) 对于EntityFrameworkCore,提供了EfCoreRepositoryBase<TDbContext, TEntity, TPrimaryKey>的泛型版本的实现方式。
2) 对于NHibernate,提供了NhRepositoryBase<TEntity, TPrimaryKey>的泛型版本的实现方式
领域服务(Domain service):
当处理的业务规则跨越两个(及以上)实体时,应该写在领域服务方法里面。
领域事件(Domain Event):
在领域层某些特定情况发生时可以触发领域事件,并且在相应地方捕获并处理它们。
工作单元(Unit of Work):
工作单元是一种设计模式,用于维护一个由已经被修改(如增加、删除和更新等)的业务对象组成的列表。它负责协调这些业务对象的持久化工作及并发问题。
多语言(Localization):
定义一种语言对应一个文件,把应用中所有需要进行多语言转换的描述,都可以写在这个目录中。
- XXX.EntityFrameworkCore(基础设施层):
提供通用技术来支持更高的层。例如基础设施层的仓储(Repository)可通过ORM来实现数据库交互。当在领域层中为定义了仓储接口,应该在基础设施层中实现这些接口。
可以使用ORM工具,例如EntityFrameworkCore或NHibernate。ABP的基类已经提供了对这两种ORM工具的支持。还有数据迁移等。
- XXX.Web.Mvc(展现层):
提供视图界面与用户进行交互操作。
- XXX.Web.Host:
这里在abp中主要是提供接口,可以是解决方案内部使用接口,可以是与移动端等其他端口连接的接口。可以自动生成WebAPI接口,供客户端调用;也可以在脚本中进行调用。
仓储的注意事项
- 仓储实现类方法中,ABP自动进行数据库连接的开启和关闭。
- 仓储方法被调用时,数据库连接自动开启事务。
- 当仓储方法调用另外一个仓储的方法,它们实际上共享的是同一个数据库连接和事务。
- 仓储对象都是暂时性的,因为IRepository接口默认继承自ITransientDependency接口。所以,仓储对象只有在需要注入的时候,才会由Ioc容器自动创建新实例。
- ABP默认的泛型仓储功能满足我们大部分的CURD操作需求。当有默认的仓储功能不满足需求的情况下,可以创建自己定制化的仓储实现类。
6、为什么需要通过dto进行数据传输?
一般来说,使用DTO进行数据传输具有以下好处。
- 数据隐藏
- 序列化和延迟加载问题
- ABP对DTO提供了约定类以支持验证
- 参数或返回值改变,通过Dto方便扩展
- DTO类被用来在 基础设施层 和 应用层 传递数据
7、Dto规范(灵活应用)
- ABP建议命名输入/输出参数为:MethodNameInput和MethodNameOutput,并为每个应用服务方法定义单独的输入和输出DTO(如果为每个方法的输入输出都定义一个dto,那将有一个庞大的dto类需要定义维护。一般通过定义一个公用的dto进行共用)
- 即使你的方法只接受/返回一个参数,也最好是创建一个DTO类
- 一般会在对应实体的应用服务文件夹下新建Dtos文件夹来管理Dto类。
- 定义完DTO,是不是脑袋有个疑问,我在用DTO在展现层与应用服务层进行数据传输,但最终这些DTO都需要转换为实体才能与数据库直接打交道啊。如果每个dto都要自己手动去转换成对应实体,这个工作量也是不可小觑啊。
8、在Abp中有两种方式创建映射规则:
特性数据注解方式:
AutoMapFrom、AutoMapTo 特性创建单向映射
AutoMap 特性创建双向映射
代码创建映射规则:
Mapper.CreateMap<source, destination>();
9、种子数据
Migrations文件夹下有个SeedData文件夹,顾名思义,这个文件夹下的类主要是用来进行预置种子数据的。
JK.ABP.EntityFrameworkCore.Seed下面添加自己的种子数据,可以模仿默认的类来写,写好之后,在JK.ABP.EntityFrameworkCore.Seed.Host下面的InitialHostDbBuilder中添加创建代码new DefaultJKExCreator(_context).Create();
在程序包管理器控制台,输入Update-Database,回车执行迁移。执行成功后,查看数据库,Tasks表创建成功,且表中已存在两条测试数据
10、工作单元UnitOfWork
UnitOfWork还有三种使用方式:过程式、惯例、声明式
using(var unitOfWork = _unitOfWorkManager.Begin()) { 。。。 unitOfWork.Complete(); }
namespace Abp.Domain.Uow { /// <summary> /// This class is used to register interceptor for needed classes for Unit Of Work mechanism. /// </summary> internal static class UnitOfWorkRegistrar { /// <summary> /// Initializes the registerer. /// </summary> /// <param name="iocManager">IOC manager</param> public static void Initialize(IIocManager iocManager) { iocManager.IocContainer.Kernel.ComponentRegistered += ComponentRegistered; //利用Castle这个IOC的ComponentRegistered事件来注册拦截器 } private static void ComponentRegistered(string key, IHandler handler) { if (UnitOfWorkHelper.IsConventionalUowClass(handler.ComponentModel.Implementation)) //惯例 { //Intercept all methods of all repositories. handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(UnitOfWorkInterceptor))); } else if (handler.ComponentModel.Implementation.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Any(UnitOfWorkHelper.HasUnitOfWorkAttribute)) {
//这里就是声明式 //Intercept all methods of classes those have at least one method that has UnitOfWork attribute. //TODO: Intecept only UnitOfWork methods, not other methods! handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(UnitOfWorkInterceptor))); } } } }
internal static class UnitOfWorkHelper { /// <summary> /// Returns true if UOW must be used for given type as convention. /// </summary> /// <param name="type">Type to check</param> public static bool IsConventionalUowClass(Type type) //惯例 { return typeof(IRepository).IsAssignableFrom(type) || typeof(IApplicationService).IsAssignableFrom(type); } /// <summary> /// Returns true if given method has UnitOfWorkAttribute attribute. /// </summary> /// <param name="methodInfo">Method info to check</param> public static bool HasUnitOfWorkAttribute(MemberInfo methodInfo) //声明 { return methodInfo.IsDefined(typeof(UnitOfWorkAttribute), true); } ... }
过程、惯例,都没问题,声明式是要注意的:
声明式的这些限制,其实是由拦截器的实现机制引起的,ABP的拦截器是用Castle DynamicProxy 动态代理来做的,动态代理是在运行时生成(使用.Net emit)一个新类(继承于原类或接口),拦截的method, 都是用override来插入代码的, 所以只能支持Interface或Virtual的方法。
总结:ABP中大量使用了AOP(面向切面编程),分离了横切关注点:Authorization, Validation, Exception Handling, Logging, Localization, Database Connection Management, Setting Management, Audit Logging;实现机理是用动态代理做的拦截器, 作为开发者对这个机理的彻底了解,有助于我更好的使用框架,也有助于用类似的方法做我们自己的AOP,毕竟AOP是我辈热衷于OOP的开发者必须掌握的技术!