ABP框架的体系结构及模块系统讲解

DDD分层
为了减少复杂性和提高代码的可重用性,采用分层架构是一种被广泛接受的技术。
为了实现分层的体系结构,ABP遵循DDD(领域驱动设计)的原则,将分为四个层次:

  • 展现层(Presentation):提供一个用户界面,实现用户交互操作。
  • 应用层(Application):进行展现层与领域层之间的协调,协调业务对象来执行特定的应用程序的任务。它不包含业务逻辑。
  • 领域层(Domain):包括业务对象和业务规则,这是应用程序的核心层。
  • 基础设施层(Infrastructure):提供通用技术来支持更高的层。例如基础设施层的仓储(Repository)可通过ORM来实现数据库交互。

根据实际需要,可能会有额外添加的层。例如:

分布式服务层(Distributed Service):用于公开应用程序接口供远程客户端调用。比如通过ASP.NET Web API和WCF来实现。
这些都是常见的以领域为中心的分层体系结构。不同的项目在实现上可能会有细微的差别。

ABP的体系结构

一个简单的解决方案,大致包含5个项目:

每一层可以用一个或多个程序集来实现。
1.领域层(Domain)
        领域层就是业务层,是一个项目的核心,所有业务规则都应该在领域层实现。
2.实体(Entity)
  实体代表业务领域的数据和操作,在实践中,通过用来映射成数据库表。
3.仓储(Repository)
  仓储用来操作数据库进行数据存取。仓储接口在领域层定义,而仓储的实现类应该写在基础设施层。
4.领域服务(Domain service)
  当处理的业务规则跨越两个(及以上)实体时,应该写在领域服务方法里面。
5.领域事件(Domain Event)
  在领域层某些特定情况发生时可以触发领域事件,并且在相应地方捕获并处理它们。
6.工作单元(Unit of Work)
  工作单元是一种设计模式,用于维护一个由已经被修改(如增加、删除和更新等)的业务对象组成的列表。它负责协调这些业务对象的持久化工作及并发问题。

应用层(Application)
应用层提供一些应用服务(Application Services)方法供展现层调用。一个应用服务方法接收一个DTO(数据传输对象)作为输入参数,使用这个输入参数执行特定的领域层操作,并根据需要可返回另一个DTO。在展现层到领域层之间,不应该接收或返回实体(Entity)对象,应该进行DTO映射。一个应用服务方法通常被认为是一个工作单元(Unit of Work)。用户输入参数的验证工作也应该在应用层实现。ABP提供了一个基础架构让我们很容易地实现输入参数有效性验证。建议使用一种像AutoMapper这样的工具来进行实体与DTO之间的映射。
 
基础设施层(Infrastructure)
当在领域层中为定义了仓储接口,应该在基础设施层中实现这些接口。可以使用ORM工具,例如EntityFramework或NHibernate。ABP的基类已经提供了对这两种ORM工具的支持。数据库迁移也被用于这一层。
 
WEB与展现层(Web & Presentation)
Web层使用ASP.NET MVC和Web API来实现。可分别用于多页面应用程序(MPA)和单页面应用程序(SPA)。
在SPA中,所有资源被一次加载到客户端浏览器中(或者先只加载核心资源,其他资源懒加载),然后通过AJAX调用服务端WebApi接口获取数据,再根据数据生成HTML代码。不会整个页面刷新。现在已经有很多SPA的JS框架,例如: AngularJs、 DurandalJs、BackboneJs、EmberJs。 ABP可以使用任何类似的前端框架,但是ABP提供了一些帮助类,让我们更方便地使用AngularJs和DurandalJs。
在经典的多页面应用(MPA)中,客户端向服务器端发出请求,服务器端代码(ASP.NET MVC控制器)从数据库获得数据,并且使用Razor视图生成HTML。这些被生成后的HTML页面被发送回客户端显示。每显示一个新的页面都会整页刷新。
SPA和MPA涉及到完全不同的体系结构,也有不同的应用场景。一个管理后台适合用SPA,博客就更适合用MPA,因为它更利于被搜索引擎抓取。
SignalR是一种从服务器到客户端发送推送通知的完美工具。它能给用户提供丰富的实时的体验。
已经有很多客户端的Javascript框架或库,JQuery是其中最流行的,并且它有成千上万免费的插件。使用Bootstrap可以让我们更轻松地完成写Html和CSS的工作。
ABP也实现了根据Web API接口自动创建 Javascript的代码函数,来简化JS对Web Api的调用。还有把服务器端的菜单、语言、设置等生成到JS端。(但是在我自己的项目中,我是把这些自动生成功能关闭的,因为必要性不是很大,而这些又会比较影响性能)。
ABP会自动处理服务器端返回的异常,并以友好的界面提示用户。

ABP模块系统
ABP框架提供了创建和组装模块的基础,一个模块能够依赖于另一个模块。在通常情况下,一个程序集就可以看成是一个模块。在ABP框架中,一个模块通过一个类来定义,而这个类要继承自AbpModule。
译者注:如果学习过Orchard的朋友,应该知道module模块的强大了。模块的本质就是可重用性,你可以在任意的地方去调用,而且通过实现模块,你写的模块也可以给别人用。
Assembly程序集:Assembly是一个包含来程序的名称,版本号,自我描述,文件关联关系和文件位置等信息的一个集合。最简单的理解就是:一个你自己写的类库生成的dll就可以看做是一个程序集,这个程序集可以包括很多类,类又包括很多方法等。
.net可以通过反射获取一个程序集中的类以及方法。
下面的例子,我们开发一个可以在多个不同应用中被调用MybolgApplication模块,代码如下:

public class MyBlogApplicationModule : AbpModule //定义
{
 public override void Initialize() //初始化
 {
 IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
 //这行代码的写法基本上是不变的。它的作用是把当前程序集的特定类或接口注册到依赖注入容器中。
 }
}

ABP框架会扫描所有的程序集,并且发现AbpModule类中所有已经导入的所有类,如果你已经创建了包含多个程序集的应用,对于ABP,我们的建议是为每一个程序集创建一个Module(模块)。
生命期事件
在一个应用中,abp框架调用了Module模块的一些指定的方法来进行启动和关闭模块的操作。我们可以重载这些方法来完成我们自己的任务。
ABP框架通过依赖关系的顺序来调用这些方法,假如:模块A依赖于模块B,那么模块B要在模块A之前初始化,模块启动的方法顺序如下:

  • PreInitialize-B
  • PreInitialize-A
  • Initialize-B
  • Initialize-A
  • PostInitialize-B
  • PostInitialize-A

下面是具体方法的说明:

1.PreInitialize
预初始化:当应用启动后,第一次会调用这个方法。在依赖注入注册之前,你可以在这个方法中指定自己的特别代码。举个例子吧:假如你创建了一个传统的登记类,那么你要先注册这个类(使用IocManager对登记类进行注册),你可以注册事件到IOC容器。等。
2.Initialize
初始化:在这个方法中一般是来进行依赖注入的注册,一般我们通过IocManager.RegisterAssemblyByConvention这个方法来实现。如果你想实现自定义的依赖注入,那么请参考依赖注入的相关文档。
3.PostInitialize
提交初始化:最后一个方法,这个方法用来解析依赖关系。
4.Shutdown
关闭:当应用关闭以后,这个方法被调用。
 
模块依赖(Module dependencies)
Abp框架会自动解析模块之间的依赖关系,但是我们还是建议你通过重载GetDependencies方法来明确的声明依赖关系。

[DependsOn(typeof(MyBlogCoreModule))]//通过注解来定义依赖关系
public class MyBlogApplicationModule : AbpModule
{
 public override void Initialize()
 {
  IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
 }
}

例如上面的代码,我们就声明了MyBlogApplicationModule和MyBlogCoreModule的依赖关系(通过属性attribute),MyBlogApplicationModule这个应用模块依赖于MyBlogCoreModule核心模块,并且,MyBlogCoreModule核心模块会在MyBlogApplicationModule模块之前进行初始化。
 
如何自定义的模块方法
我们自己定义的模块中可能有方法被其他依赖于当前模块的模块调用,下面的例子,假设模块2依赖于模块1,并且想在预初始化的时候调用模块1的方法。

public class MyModule1 : AbpModule
{
 public override void Initialize() //初始化模块
 {
 IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());//这里,进行依赖注入的注册。
 }
 public void MyModuleMethod1()
 {
 //这里写自定义的方法。
 }
}
[DependsOn(typeof(MyModule1))]
public class MyModule2 : AbpModule
{
 private readonly MyModule1 _myModule1;
 public MyModule2(MyModule1 myModule1)
 {
 _myModule1 = myModule1;
 }
 public override void PreInitialize()
 {
 _myModule1.MyModuleMethod1(); //调用MyModuleMethod1的方法。
 }
 public override void Initialize()
 {
 IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
 }
}

就这样,就把模块1注入到了模块2,因此,模块2就能调用模块1的方法了。

(0)

相关推荐

  • ABP框架的基础配置及依赖注入讲解

    配置ABP 配置是通过在自己模块的PreInitialize方法中来实现的 代码示例如下: public class SimpleTaskSystemModule : AbpModule { public override void PreInitialize() { //在你的应用中添加语言包,这个是英语和作者的土耳其语. Configuration.Localization.Languages.Add(new LanguageInfo("en", "English&quo

  • 详解ABP框架中的日志管理和设置管理的基本配置

    日志管理 Server side(服务器端) ASP.NET Boilerplate使用Castle Windsor's logging facility日志记录工具,并且可以使用不同的日志类库,比如:Log4Net, NLog, Serilog... 等等.对于所有的日志类库,Castle提供了一个通用的接口来实现,我们可以很方便的处理各种特殊的日志库,而且当业务需要的时候,很容易替换日志组件. 译者注释:Castle是什么:Castle是针对.NET平台的一个开源项目,从数据访问框架ORM到

  • 解析ABP框架中的数据传输对象与应用服务

    数据传输对象(DTOs) 数据传输对象(Data Transfer Objects)用于应用层和展现层的数据传输. 展现层传入数据传输对象(DTO)调用一个应用服务方法,接着应用服务通过领域对象执行一些特定的业务逻辑并且返回DTO给展现层.这样展现层和领域层被完全分离开了.在具有良好分层的应用程序中,展现层不会直接使用领域对象(仓库,实体). 1.数据传输对象的作用: 为每个应用服务方法创建DTO看起来是一项乏味耗时的工作.但如果你正确使用它们,这将会解救你的项目.为啥呢? (1)抽象领域层 (

  • 详解ABP框架中领域层的领域事件Domain events

    在C#中,一个类可以定义其专属的事件并且其它类可以注册该事件并监听,当事件被触发时可以获得事件通知.这对于对于桌面应用程序或独立的Windows Service来说非常有用.但是, 对于Web应用程序来说会有点问题,因为对象是根据请求(request)被创建并且它们的生命周期都很短暂.我们很难注册其它类别的事件.同样地,直接注册其它类别的事件也造成了类之间的耦合性. 在应用系统中,领域事件被用于解耦并且重用(re-use)商业逻辑. 事件总线 事件总线为一个单体(singleton)的对象,它由

  • 基于ASP.NET MVC的ABP框架入门学习教程

    为什么使用ABP 我们近几年陆续开发了一些Web应用和桌面应用,需求或简单或复杂,实现或优雅或丑陋.一个基本的事实是:我们只是积累了一些经验或提高了对,NET的熟悉程度. 随着软件开发经验的不断增加,我们发现其实很多工作都是重复机械的,而且随着软件复杂度的不断提升,以往依靠经验来完成一些简单的增删改查的做法已经行不通了.特别是用户的要求越来越高,希望添加的功能越来多,目前这种开发模式,已经捉襟见肘.我很难想象如何在现有的模式下进行多系统的持续集成并添加一些新的特性. 开发一个系统时,我们不可避免

  • 解析ABP框架中的事务处理和工作单元

    通用连接和事务管理方法 连接和事务管理是使用数据库的应用程序最重要的概念之一.当你开启一个数据库连接,什么时候开始事务,如何释放连接...诸如此类的. 正如大家都知道的,.Net使用连接池(connection pooling).因此,创建一个连接实际上是从连接池中取得一个连接,会这么做是因为创建新连接会有成本.如果没有任何连接存在于连接池中,一个新的连接对象会被创建并且添加到连接池中.当你释放连接,它实际上是将这个连接对象送回到连接池.这并不是实际意义上的释放.这个机制是由.Net所提供的.因

  • 详解ABP框架的参数有效性验证和权限验证

    参数有效性验证 应用程序的输入数据首先应该被检验是否有效.输入的数据能被用户或其他应用程序提交.在Web应用中,通常进行2次数据有效性检验:包括客户端检验和服务端检验.客户端的检验主要是使用户有一个好的用户体验. 首先最好是在客户端检验其表单输入的有效性并且展示给客户端的那些字段输入是无效的.但是,服务器端的校验是更关键和不可缺失的(不要只做客户端检验而不做服务器端检验). 服务器端的检验通常是被应用服务(层)执行,应用服务(层)中的方法首先检验数据的有效性,然后才使用这些通过验证的数据.ABP

  • 详解ABP框架中的数据过滤器与数据传输对象的使用

    数据过滤器(Data filters) 在数据库开发中,我们一般会运用软删除(soft-delete)模式,即不直接从数据库删除数据,而是标记这笔数据为已删除.因此,如果实体被软删除了,那么它就应该不会在应用程序中被检索到.要达到这种效果,我们需要在每次检索实体的查询语句上添加SQL的Where条件IsDeleted = false.这是个乏味的工作,但它是个容易被忘掉的事情.因此,我们应该要有个自动的机制来处理这些问题. ABP提供数据过滤器(Data filters),它使用自动化的,基于规

  • ASP.NET样板项目ABP框架的特性总结

    ABP是"ASP.NET Boilerplate Project (ASP.NET样板项目)"的简称. ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WEB应用程序的新起点,它旨在成为一个通用的WEB应用程序框架和项目模板. ASP.NET Boilerplate 基于DDD的经典分层架构思想,实现了众多DDD的概念(但没有实现所有DDD的概念). ABP的官方网站:http://www.aspnetboilerplate.com ABP在Github上的开源

  • ABP框架中的日志功能完全解析

    ASP.NET Boilerplate使用Castle Windsor's logging facility日志记录工具,并且可以使用不同的日志类库,比如:Log4Net, NLog, Serilog... 等等.对于所有的日志类库,Castle提供了一个通用的接口来实现,我们可以很方便的处理各种特殊的日志库,而且当业务需要的时候,很容易替换日志组件. 译者注释:Castle是什么:Castle是针对.NET平台的一个开源项目,从数据访问框架ORM到IOC容器,再到WEB层的MVC框架.AOP,

  • 解析ABP框架领域层中的实体类与仓储类

    领域层 实体是DDD(领域驱动设计)的核心概念之一.Eric Evans是这样描述的"很多对象不是通过它们的属性定义的,而是通过一连串的连续性事件和标识定义的"(引用领域驱动设计一书). 译者注:对象不是通过它们的属性来下根本性的定义,而应该是通过它的线性连续性和标识性定义的..所以,实体是具有唯一标识的ID且存储在数据库中.实体通常被映射成数据库中的一个表. 实体类(Entity classes) 在ABP中,实体继承自Entity类,请看下面示例: public class Per

  • ABP框架中导航菜单的使用及JavaScript API获取菜单的方法

    每一个WEB应用程序都有导航菜单,Abp也为用户提供了通用的创建和显示菜单方式. 创建菜单 一个应用程序可能包含不同的模块,而每个模块都可能有它自己的菜单项.在Abp中,需要创建一个派生自NavigationProvider的类来定义一个菜单项. 假设我们有一个这样的主菜单: Tasks Reports Administration 1 User Management 2 Role Management 由上可知,Administration菜单项有两个子菜单项.对应的生成方法如下: publi

  • 详解ABP框架中Session功能的使用方法

    如果一个应用程序需要登录,则它必须知道当前用户执行了什么操作.因此ASP.NET在展示层提供了一套自己的SESSION会话对象,而ABP则提供了一个可以在任何地方 获取当前用户和租户的IAbpSession接口. 关于IAbpSession 需要获取会话信息则必须实现IAbpSession接口.虽然你可以用自己的方式去实现它(IAbpSession),但是它在module-zero项目中已经有了完整的实现. 注入Session IAbpSession通常是以属性注入的方式存在于需要它的类中,不需

随机推荐