企业级应用架构设计
基本设计原则
一个设计精良的系统并不是一系列指令和修改的堆砌,其中包含很多与设计直接或间接相关的要素,与国际化标准中定义的其它质量特性相比,需要更加重视代码的可维护性,之所以选择这个特性,并不是因为其他特性和可维护性相比不重要,而是保持代码的可维护性的代码比较高,而且容易让开发者忽视,可维护是最关键的问题。
对于可维护性可分为两点解析,结构化设计是第一要素,可通过一系列的编码技术来保证。代码的可读性是另外一个重要因素。
结构化设计
结构化设计从理念诞生开始,内部隐藏的结构化设计核心原则一直是今天的指导原则,高内聚和低耦合这两个原则在面向对象世界中风光无限。内聚的衡量标准从低到高,内聚越高说明软件设计的越好。高内聚的模块意味着可维护性和可重用性,因为这些模块的外部依赖很少。低内聚的模块容易在其他模块中留下依赖,让软件变得顽固且高粘度。耦合的衡量标准从低到高,耦合越低说明软件设计越好。高内聚和低耦合唇齿相依,若系统满足这两个条件,则说明该系统基本满足高可读性、高可维护性,并易于测试和易于重用的要求。
分离关注点
在设计系统时需要考虑内聚和耦合两个因素时,分离关注点有助于实现这个目标。分离关注点的核心在于将系统拆分成各不相同且最好没有重叠的功能。分离关注点原则建议一次只处理一个关注点。这并不表示此时要将所有的其他关注点都抛之脑后,而是说当决定用某个模块来实现这个关注点之后,只需要全神贯注于实现该模块即可。从这个角度考虑,其他关注点都是不相关的。具体来说,分离关注点是通过模块化代码以及大量运用信息隐藏来实现的。模块化编码鼓励使用不同的模块来实现不同的功能,模块拥有自己的公开接口,和其他模块通信,模块同时包含大量内部信息,供自己使用。信息隐藏是一条通用的设计选择,固定的公开接口隐藏软件模块的实现细节,以降低未来修改造成的影响。
其实分离关注点第一种支持的编程理念是过程式编程,在过程式编程中,分离关注点依靠函数和过程来实现。不仅分离关注点的概念不仅限于编程语言,它超越了纯粹编程的领域。还引用了软件架构的很多方面。在面向服务架构SOA中,服务用来表示关注点。分层架构也基于分离关注点的原则构建。
面向对象设计
面向对象设计可以说是一座里程碑,面向对象设计中的一个重要的步骤就是为问题领域寻找一个赶紧灵活的抽象。若要很好的完整这一步,应该考虑的是事情而非流程。应该关注的是“什么”而非“如何”。
可重用性是面向对象理念中一个非常重要的方面,也是面向对象广泛使用的根本原因。《设计模式》总结出两种实现重用的方式:白盒重用和黑盒重用。白盒重用基于继承,黑盒重用基于对象组合。在实际设计中,尽量使用对象组合而非类型继承。在现实世界中,对象组合更加安全,易于维护和测试。在组合中,修改组合对象并不会影响到内部对象。
高级原则
开放封闭原则
开放封闭原则能够帮助软件的单元(包括类型、函数、模块)更加容易适应变化。每次发生变化时,要通过添加新代码来增强现有类型的行为,而非修改原有代码。当前这个原则最好的方式是提供一个固定的接口,然后让可能发生变化的类实现该接口,随后调用者基于该接口操作。
里氏替换原则
但某个类型派生于某个现有类型时,派生类应该能够用于任何可以使用父类的地方即多态。开放封闭原则和里氏替换原则有着紧密的关系,任何使用违反里氏替换原则的类型的方法都无法满足开放封闭原则。
依赖倒置原则
依赖倒置原则中的倒置表示在实现过程中应采用自顶向下的方式,且应该关注于高层次模块的工作流,而非低层次的模块具体的实现。从这点考虑,低层次模块可以直接插入到高层次模块中。
知识点
ASP.NET MVC请求是如何进入管道的
请求处理管道
请求管道是用于处理HTTP请求的模块组合,在ASP.NET中请求管道有两大核心组件IHttpModule
和IHttpHandler
。所有HTTP请求会进入IHttpHandler
,有IHttpHandler
进行最终的处理,而IHttpModule
通过订阅HttpApplication
对象中的事件,可在IHttpHandler
对HTTP请求进行处理前对请求进行预处理或IHttpHandler
对HTTP请求之后进行再次处理。
IIS7之前请求处理管道分为两个:IIS请求处理管道和ASP.NET管道,若客户端请求静态资源则只有IIS管道进行处理,而ASP.NET管道不会处理该请求。从IIS7开始两个管道合二为一,称为集成管道。
ASP.NET MVC处理管道
ASP.NET MVC的请求管理和ASP.MET请求管道基本类似,
ASP.NET MVC请求处理流程
- 浏览器发送HTTP请求
- Web服务器IIS
- ISAPIRuntime
- HttpWorkRequest
- HttpRuntime
- HttpContext
- 寻找Global文件并编译
- 确保Global文件中Application_Start被调用
- 创建HttpApplication,使用了池(栈)。如果池中没有根据Global文件编译的类型,则通过反射的形式创建出HttpApplication。
- 获取所有在Web.config配置文件中的HttpModules,此时System.Web.Routing下UrlRoutingModule也被获取,执行每个Module下的Init方法。UrlRoutingModule的Init方法完成了请求管道第七个事件的注册。
- 进入管道
- 第七个事件触发执行相应方法,完成MVCHandler的创建。
-
走到请求管道中的11与12事件之间,执行MVCHandler中的ProcessRequest方法,该方法寻找控制器和方法,执行方法中的代码,最终寻找视图并渲染。
搭建框架
新建空白解决方案并命名为OA
,在空白项目中添加类库与ASP.NET MVC的Web应用程序。
- 添加类库
OA.Common
公共帮助类库 - 添加类库
OA.Model
EF实体数据模型 - 添加类库
OA.DAL
- 添加类库
OA.IDAL
数据访问层 - 添加类库
OA.DALFactory
抽象工厂类 - 添加类库
OA.BLL
- 添加类库
OA.IBLL
业务逻辑层 - 添加Web应用程序
OA.WebApp
,并将其设置为解决方案的默认启动项。
模型层
创建数据模型
- 在
OA.Model
中添加ADO.NET实体数据模型并命名为OAModel
,并采用Code First
编码优先的方式创建数据模型。
- 查看
OA.Model
中的App.config
应用配置文件,添加本地数据库连接字符串。
<connectionStrings>
<add
name="OAModel"
connectionString="data source=(LocalDb)\MSSQLLocalDB;initial catalog=OA.Model.OAModel;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework"
providerName="System.Data.SqlClient" />
</connectionStrings>
- 在模型中添加实体
$ vim OA.Model/OAModel.cs
namespace OA.Model
{
using System;
using System.Data.Entity;
public class OAModel : DbContext
{
//上下文已配置为从应用程序的配置文件(App.config 或 Web.config)使用“OAModel”连接字符串
public OAModel(): base("name=OAModel")
{
}
//为要在模型中包含的每种实体类型都添加 DbSet。
public virtual DbSet<UserInfo> UserInfo { get; set; }
}
public class UserInfo
{
public int ID { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
public bool Status { get; set; }
public int Sort { get; set; }
public String Remark { get; set; }
}
}
数据访问层
数据访问层的设计很大程度上取决于项目干系人需求的影响。例如,数据访问层应该持久化对象模型还是简单的值的集合呢?数据访问层应该支持一种数据库还是多种数据库呢?
数据库独立性
数据访问层是系统中唯一知道并使用连接字符串和数据表名的地方,考虑到这些,数据访问层必须要依赖于数据库管理系统DBMS。对于外部观察者,数据访问层应该是一个黑盒,可以插入到现有系统中,封装了为某个特定DBMS实现的读取和写入的操作。
像插入一样可以配置
通常来说,数据库独立性需要一套普通的、跨库的应用编程接口。实现真正数据库独立需要将数据库访问层作为一个黑盒,该黑盒提供了一个固定的接口,并从配置文件中动态地读取当前数据库访问层组件的细节。还有一种实现数据库独立性的做法是使用对象/关系映射工具ORM。ORM提供一套公共的API,让你仅需简单修改配置参数就可以切换到另一个数据库。不过,有时候项目允许你使用ORM,有时却不行。
持久化应用程序的对象模型
无论何种形式,数据访问层都必须能够持久化应用程序的数据。若必须提供对象模型,那么数据访问层要能够将模型持久化至关系型结构中。当然,会造成臭名昭著的“对象关系阻抗失调”问题。关系型数据库实际上存放的数据元组,而对象模型则构造出一张对象图。因此,两个模型之间自然需要映射,这也是数据访问层的主要功能。持久化应用程序的对象模型是指将数据加载到新创建对象模型和将某个实例的内容写回数据库的能力。无论选择什么样的DBMS或物理上的表结构,持久化功能都不应该受到影响。依照领域模型模式设计的对象模型并不了解数据访问层的存在,不过若对象模型属于活动记录,那么数据访问层内嵌在实现模型的所有框架中。
数据访问层的职责
数据访问层对使用者来说有4个职责:
首先,数据访问层需要将数据持久化至物理存储中,并为外部世界提供CRUD服务。
其次,数据访问层还要处理其接收的所有数据相关的请求。
再次,数据访问层必须满足事务性需求。
最后,数据访问层也要合理的处理并发。
从概念角度,数据访问层可以看作是封装了4种服务的黑箱。
创建数据访问层接口
-
为数据访问层接口添加对模型层的引用
创建基础数据访问层接口,定义公共的数据操作方法接口,其它数据访问接口均继承于它。
定义基础的数据操作方法,如CURD与分页。
$ vim OA.IDAL/IBaseDal.cs
using System;
using System.Linq;
using System.Linq.Expressions;
namespace OA.IDAL
{
public interface IBaseDal<T> where T : class, new()
{
/*查询*/
IQueryable<T> Get(Expression<Func<T, bool>> whereLambda);
/**
* 分页
* s为方法泛型,表示排序字段的数据类型。
*/
IQueryable<T> Page<s>(int pageIndex, int pageSize, out int totalCount, Expression<Func<T, bool>> whereLambda, Expression<Func<T, s>> orderByLambda, bool isAsc);
/*删除*/
bool Delete(T entity);
/*更新*/
bool Update(T entity);
/*创建*/
T Create(T entity);
}
}
- 创建具体数据访问层接口
$ vim OA.IDAL/IUserInfo.cs
using OA.Model;
namespace OA.IDAL
{
/*数据访问接口*/
public interface IUserInfoDal:IBaseDal<UserInfo>
{
}
}
创建数据持久层类并实现其接口
-
添加数据访问层DAL对模型层Model和数据访问层接口IDAL的引用
- 添加对EF的引用
查看模型层中对EF的引用分别为EntityFramework
和EntityFramework.SqlServer
查看模型层中EF引用的版本,注意整个解决方案中所有引入EF的位置必须保证版本一致。
$ vim OA.Model/App.config
<configSections>
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
</configSections>
在数据访问层添加临时的模型层,系统会自动加载所需的EF程序集的引用,添加后删除该模型文件。
- 创建数据操作类并实现公共的基础接口方法
$ vim OA.DAL/UserInfoDal.cs
using OA.IDAL;
using OA.Model;
using System;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;
namespace OA.DAL
{
public class UserInfoDal : IUserInfoDal
{
OAModel db = new OAModel();
public UserInfo Create(UserInfo entity)
{
db.UserInfo.Add(entity);
return entity;
}
public bool Delete(UserInfo entity)
{
db.Entry<UserInfo>(entity).State = EntityState.Deleted;
return db.SaveChanges() > 0;
}
public IQueryable<UserInfo> Get(Expression<Func<UserInfo, bool>> whereLambda)
{
return db.UserInfo.Where<UserInfo>(whereLambda);
}
public IQueryable<UserInfo> Page<s>(int pageIndex, int pageSize, out int totalCount, Expression<Func<UserInfo, bool>> whereLambda, Expression<Func<UserInfo, s>> orderByLambda, bool isAsc)
{
var temp = db.UserInfo.Where<UserInfo>(whereLambda);
totalCount = temp.Count();
int skip = (pageIndex - 1) * pageSize;
if (isAsc)
{
temp = temp.OrderBy<UserInfo, s>(orderByLambda).Skip<UserInfo>(skip).Take<UserInfo>(pageSize);
}
else
{
temp = temp.OrderByDescending<UserInfo, s>(orderByLambda).Skip<UserInfo>(skip).Take<UserInfo>(pageSize);
}
return temp;
}
public bool Update(UserInfo entity)
{
db.Entry<UserInfo>(entity).State = EntityState.Modified;
return db.SaveChanges() > 0;
}
}
}
- 由于每个数据操作类都需要实现基础操作,因此使用继承的方式添加公共的数据操作父类。
基类:无需实现IBaseDal
$ vim OA.DAL/BaseDal.cs
using OA.Model;
using System;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;
namespace OA.DAL
{
public class BaseDal<T> where T: class, new()
{
OAModel db = new OAModel();
public T Create(T entity)
{
db.Set<T>().Add(entity);
db.SaveChanges();
return entity;
}
public bool Delete(T entity)
{
db.Entry<T>(entity).State = EntityState.Deleted;
return db.SaveChanges() > 0;
}
public IQueryable<T> Get(Expression<Func<T, bool>> whereLambda)
{
return db.Set<T>().Where<T>(whereLambda);
}
public IQueryable<T> Page<s>(int pageIndex, int pageSize, out int totalCount, Expression<Func<T, bool>> whereLambda, Expression<Func<T, s>> orderByLambda, bool isAsc)
{
var temp = db.Set<T>().Where<T>(whereLambda);
totalCount = temp.Count();
int skip = (pageIndex - 1) * pageSize;
if (isAsc)
{
temp = temp.OrderBy<T, s>(orderByLambda).Skip<T>(skip).Take<T>(pageSize);
}
else
{
temp = temp.OrderByDescending<T, s>(orderByLambda).Skip<T>(skip).Take<T>(pageSize);
}
return temp;
}
public bool Update(T entity)
{
db.Entry<T>(entity).State = EntityState.Modified;
return db.SaveChanges() > 0;
}
}
}
子类:注意需要先继承后实现
$ OA.DAL/UserInfoDal.cs
using OA.IDAL;
using OA.Model;
namespace OA.DAL
{
public class UserInfoDal : BaseDal<UserInfo>, IUserInfoDal
{
}
}
数据会话层
- 数据会话层位于数据操作层与业务处理层之间
- 数据会话层封装了所有数据操作类实例的创建
- 业务处理层通过数据会话层来获取要操作数据持久层中操作类的实例
- 数据会话层本质就是一个工厂类,负责对象的创建。
- 将业务处理层与数据持久层进行解耦,提供一个数据访问的统一访问点。
- 添加引用
- 添加对数据模型
OA.Model
的引用 - 添加对数据操作层接口
OA.IDAL
的引用 - 添加对数据操作层
OA.DAL
的引用
- 创建数据会话层类
- 数据会话层封装了所有数据操作类的实例的创建,本质是一个工厂类。
- 将业务层与数据层解耦
- 提供数据访问的统一访问点
$ vim OA.DALFactory/DBSession.cs
using OA.DAL;
using OA.IDAL;
using OA.Model;
namespace OA.DALFactory
{
/**
* 数据会话层
* 1.本质是一个工厂类
* 2.负责完成所有数据操作类实例的创建
* 3.业务处理层通过数据会话层来获取操作数据类的实例
* 4.数据会话层将业务层和数据层解耦
*/
public class DBSession
{
/*负责完成所有数据操作类实例的创建*/
private IUserInfoDal _UserInfoDal;
public IUserInfoDal UserInfoDal
{
get
{
if(_UserInfoDal == null)
{
_UserInfoDal = new UserInfoDal();
}
return _UserInfoDal;
} ,
set
{
_UserInfoDal = value;
}
}
}
- 在数据会话层中添加对所有数据保存的方法
数据会话层OA.DALFactory中添加对EF的引用
$ vim OA.DALFactory/DbSession.cs
using OA.DAL;
using OA.IDAL;
using OA.Model;
namespace OA.DALFactory
{
/**
* 数据会话层
* 1.本质是一个工厂类
* 2.负责完成所有数据操作类实例的创建
* 3.业务处理层通过数据会话层来获取操作数据类的实例
* 4.数据会话层将业务层和数据层解耦
* 5.完成所有数据的保存
*/
public class DbSession
{
/*负责完成所有数据操作类实例的创建*/
private IUserInfoDal _UserInfoDal;
public IUserInfoDal UserInfoDal
{
get
{
if(_UserInfoDal == null)
{
_UserInfoDal = new UserInfoDal();
}
return _UserInfoDal;
}
set
{
_UserInfoDal = value;
}
}
/**
* 工作单元设计模式 - 完成所有数据的保存
* 一个业务中涉及到对多张表的操作
* 连接一次数据库完成对多张表的数据操作
*/
OAModel db = new OAModel();
public bool SaveChanges()
{
return db.SaveChanges() > 0;
}
}
}
- 将数据持久层中公共的基础数据持久类中所有的
SaveChanges()
取消
$ vim OA.DAL/BaseDal.cs
using OA.Model;
using System;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;
namespace OA.DAL
{
public class BaseDal<T> where T: class, new()
{
OAModel db = new OAModel();
public T Create(T entity)
{
db.Set<T>().Add(entity);
//db.SaveChanges();
return entity;
}
public bool Delete(T entity)
{
db.Entry<T>(entity).State = EntityState.Deleted;
//return db.SaveChanges() > 0;
return true;
}
public IQueryable<T> Get(Expression<Func<T, bool>> whereLambda)
{
return db.Set<T>().Where<T>(whereLambda);
}
public IQueryable<T> Page<s>(int pageIndex, int pageSize, out int totalCount, Expression<Func<T, bool>> whereLambda, Expression<Func<T, s>> orderByLambda, bool isAsc)
{
var temp = db.Set<T>().Where<T>(whereLambda);
totalCount = temp.Count();
int skip = (pageIndex - 1) * pageSize;
if (isAsc)
{
temp = temp.OrderBy<T, s>(orderByLambda).Skip<T>(skip).Take<T>(pageSize);
}
else
{
temp = temp.OrderByDescending<T, s>(orderByLambda).Skip<T>(skip).Take<T>(pageSize);
}
return temp;
}
public bool Update(T entity)
{
db.Entry<T>(entity).State = EntityState.Modified;
//return db.SaveChanges() > 0;
return true;
}
}
}
- EF线程内唯一
目前的问题是,EF在数据操作层中会使用,EF在数据会话层中也会使用到,这是两个不同的对象。而在一个请求中只能创建一个EF实例,也就是线程内唯一。对此,因采用工厂模式。
如果在DALFactory
中创建一个工厂类封装EF实例的创建,会出现一个问题。由于DALFactory
已经引入了DAL,EF若在DALFactory
中创建,那么DAL也必须引用DALFactory
,此时也就成了相互循环引用。所以,应该将负责EF实例创建的工厂类DbContextFactory
放到DAL中。
$ OA.DAL/DbContextFactory.cs
using OA.Model;
using System.Data.Entity;
using System.Runtime.Remoting.Messaging;
namespace OA.DAL
{
/**
* 负责创建EF数据操作上下文实例
* 使用工厂模式且必须保证线程内唯一
*/
public class DbContextFactory
{
public static DbContext CreateDbContext()
{
DbContext dbContext = (DbContext)CallContext.GetData("dbContext");
if (dbContext == null)
{
dbContext = new OAModel();
CallContext.SetData("dbContext", dbContext);
}
return dbContext;
}
}
}
在数据会话层DbSession和数据处理层中公共基类BaseDal中调用CreateDbContext方法,完成EF实例的创建。
OAModel db = new OAModel();
将原来db的实例化的方式修改为通过DbContextFactory创建
public DbContext db
{
get
{
return DbContextFactory.CreateDbContext();
}
}
简化方式
DbContext db = DbContextFactory.CreateDbContext();
在DbSession中获取db实例
$ vim OA.DALFactory/DbSession.cs
using OA.DAL;
using OA.IDAL;
using System.Data.Entity;
namespace OA.DALFactory
{
/**
* 数据会话层
* 1.本质是一个工厂类
* 2.负责完成所有数据操作类实例的创建
* 3.业务处理层通过数据会话层来获取操作数据类的实例
* 4.数据会话层将业务层和数据层解耦
* 5.完成所有数据的保存
*/
public class DbSession
{
/*负责完成所有数据操作类实例的创建*/
private IUserInfoDal _UserInfoDal;
public IUserInfoDal UserInfoDal
{
get
{
if(_UserInfoDal == null)
{
_UserInfoDal = new UserInfoDal();
}
return _UserInfoDal;
}
set
{
_UserInfoDal = value;
}
}
/**
* 工作单元 设计模式
* 完成所有数据的保存
* 一个业务中涉及到对多张表的操作
* 连接一次数据库完成对多张表的数据操作
*/
//OAModel db = new OAModel();
DbContext db = DbContextFactory.CreateDbContext();
public bool SaveChanges()
{
return db.SaveChanges() > 0;
}
}
}
在BaseDal中获取db实例
$ vim OA.DAL/BaseDal.cs
using OA.Model;
using System;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;
namespace OA.DAL
{
public class BaseDal<T> where T: class, new()
{
//OAModel db = new OAModel();
//public DbContext db
//{
// get
// {
// return DbContextFactory.CreateDbContext();
// }
//}
DbContext db = DbContextFactory.CreateDbContext();
public T Create(T entity)
{
db.Set<T>().Add(entity);
//db.SaveChanges();
return entity;
}
public bool Delete(T entity)
{
db.Entry<T>(entity).State = EntityState.Deleted;
//return db.SaveChanges() > 0;
return true;
}
public IQueryable<T> Get(Expression<Func<T, bool>> whereLambda)
{
return db.Set<T>().Where<T>(whereLambda);
}
public IQueryable<T> Page<s>(int pageIndex, int pageSize, out int totalCount, Expression<Func<T, bool>> whereLambda, Expression<Func<T, s>> orderByLambda, bool isAsc)
{
var temp = db.Set<T>().Where<T>(whereLambda);
totalCount = temp.Count();
int skip = (pageIndex - 1) * pageSize;
if (isAsc)
{
temp = temp.OrderBy<T, s>(orderByLambda).Skip<T>(skip).Take<T>(pageSize);
}
else
{
temp = temp.OrderByDescending<T, s>(orderByLambda).Skip<T>(skip).Take<T>(pageSize);
}
return temp;
}
public bool Update(T entity)
{
db.Entry<T>(entity).State = EntityState.Modified;
//return db.SaveChanges() > 0;
return true;
}
}
}
- 将DbSession工厂类中实例化数据操作类的方式修改使用抽象工厂来完成
在表现层WebApp的配置文件Web.config
中设置程序集与命名空间。
$ vim OA.WebApp/Web.conifg
<appSettings>
<add key="webpages:Version" value="3.0.0.0"/>
<add key="webpages:Enabled" value="false"/>
<add key="ClientValidationEnabled" value="true"/>
<add key="UnobtrusiveJavaScriptEnabled" value="true"/>
<!--配置程序集与命名空间-->
<add key="AssemblyPath" value="OA.DAL"/>
<add key="NameSpace" value="OA.DAL"/>
</appSettings>
在数据会话层中引入框架中的System.Configuration
程序集
为了进一步将数据会话层与话剧操作层解耦,在数据会话层DALFactory中创建抽象工厂,以完成数据操作类的实例化。
$ vim OA.DALFactory/AbstractFactory.cs
using OA.IDAL;
using System.Configuration;
using System.Reflection;
namespace OA.DALFactory
{
/**
* 抽象工厂
* 通过反射的方式创建类的实例
*/
public class AbstractFactory
{
private static readonly string AssemblyPath = ConfigurationManager.AppSettings["AssemblyPath"];
private static readonly string NameSpace = ConfigurationManager.AppSettings["NameSpace"];
private static object CreateInstance(string className)
{
var assembly = Assembly.Load(AssemblyPath);
return assembly.CreateInstance(className);
}
public static IUserInfoDal CreateUserInfoDal()
{
string fullClassName = NameSpace + ".UserInfoDal";
return CreateInstance(fullClassName) as IUserInfoDal;
}
}
}
数据会话层调用抽象工厂完成实例化
$ vim OA.DALFactory/DbSession.cs
using OA.DAL;
using OA.IDAL;
using System.Data.Entity;
namespace OA.DALFactory
{
/**
* 数据会话层
* 1.本质是一个工厂类
* 2.负责完成所有数据操作类实例的创建
* 3.业务处理层通过数据会话层来获取操作数据类的实例
* 4.数据会话层将业务层和数据层解耦
* 5.完成所有数据的保存
*/
public class DbSession
{
/*负责完成所有数据操作类实例的创建*/
private IUserInfoDal _UserInfoDal;
public IUserInfoDal UserInfoDal
{
get
{
if(_UserInfoDal == null)
{
//_UserInfoDal = new UserInfoDal();
//使用抽象工厂来封装了类的实例的创建(解耦)
_UserInfoDal = AbstractFactory.CreateUserInfoDal();
}
return _UserInfoDal;
}
set
{
_UserInfoDal = value;
}
}
/**
* 工作单元 设计模式
* 完成所有数据的保存
* 一个业务中涉及到对多张表的操作
* 连接一次数据库完成对多张表的数据操作
*/
//OAModel db = new OAModel();
DbContext db = DbContextFactory.CreateDbContext();
public bool SaveChanges()
{
return db.SaveChanges() > 0;
}
}
}
核心在于
//_UserInfoDal = new UserInfoDal();
//使用抽象工厂来封装了类的实例的创建(解耦)
_UserInfoDal = AbstractFactory.CreateUserInfoDal();
数据会话层DbSession调用数据操作层DAL使用的是数据操作层所提供的接口IUserInfoDal,业务层BLL调用数据会话层DbSession同样采用接口的方式,因此数据会话层DbSession必须提供对业务层的接口。
为便于引用及使用,在OA.IDAL定义IDbSession接口。
$vim OA.IDAL/IDbSession.cs
using System.Data.Entity;
namespace OA.IDAL
{
/**
* 业务层BLL调用的是数据会话层的接口
*/
public interface IDbSession
{
DbContext db{get;}
bool SaveChanges();
IUserInfoDal UserInfoDal { get; set; }
}
}
在DbSession中实现IDbSession接口
$ vim OA.DALFactory/DbSession.cs
using OA.DAL;
using OA.IDAL;
using System.Data.Entity;
namespace OA.DALFactory
{
/**
* 数据会话层
* 1.本质是一个工厂类
* 2.负责完成所有数据操作类实例的创建
* 3.业务处理层通过数据会话层来获取操作数据类的实例
* 4.数据会话层将业务层和数据层解耦
* 5.完成所有数据的保存
*/
public class DbSession:IDbSession
{
/*负责完成所有数据操作类实例的创建*/
private IUserInfoDal _UserInfoDal;
public IUserInfoDal UserInfoDal
{
get
{
if(_UserInfoDal == null)
{
//_UserInfoDal = new UserInfoDal();
//使用抽象工厂来封装了类的实例的创建(解耦)
_UserInfoDal = AbstractFactory.CreateUserInfoDal();
}
return _UserInfoDal;
}
set
{
_UserInfoDal = value;
}
}
/**
* 工作单元 设计模式
* 完成所有数据的保存
* 一个业务中涉及到对多张表的操作
* 连接一次数据库完成对多张表的数据操作
*/
//OAModel db = new OAModel();
//DbContext db = DbContextFactory.CreateDbContext();
public DbContext db
{
get
{
return DbContextFactory.CreateDbContext();
}
}
public bool SaveChanges()
{
return db.SaveChanges() > 0;
}
}
}
业务逻辑层
任何复杂的软件都可以通过分层来组织,每层表示系统中的一个逻辑部分,一般来说,业务逻辑层中的模块包含了系统所需的所有功能上的算法和计算过程,并于数据层和表现层交互。抽象的说,业务逻辑层是软件中专门处理业务相关任务性能的部分。
业务逻辑层表示了系统的逻辑,此处的代码将要进行必要的决断并执行操作。在业务逻辑层的安全性意味着使用基于角色的安全原则,仅允许认证用户访问特定的业务对象。从外界看,业务逻辑层可看作是一个操作业务对象的机制。一般来说,业务对象不过是某个领域实现的实现,或是某类辅助类型,用来执行一些计算。业务逻辑层处于分层系统的中间位置,业务逻辑层的输入和输出不一定是业务对象。很多时候,架构师更加倾向于数据迁移对象在层之间交换数据。
数据迁移对象和业务对象之间的取舍一直是团队中争议的话题,建议使用数据迁移对象的理论认为,数据迁移能减少层之间的耦合,使系统更加整洁干净。不过在现实中,人们都会说复杂性已经很高,因此应该避免增加任何不必要的对象。一条使用的原则是当已经有了数百个业务对象时,或许并不应该仅仅为了设计的干净而让这个数字加倍。在这种情况下,数据迁移对象通常就是业务对象。业务对象同时包含了数据和行为,是一个可以参与到领域逻辑的完整对象。而数据迁移对象更像是一种值。即一系列数据的容器而没有相关的行为。为了序列化,业务对象中的数据会复制到数据迁移对象中。除了get/set访问器以外,数据迁移对象没有逻辑行为。数据迁移对象并不仅仅是领域对象去掉了行为,它表现了特定领域对象的一个子集,用于专门的上下文中。一般来说或领域对象是一个对象图,而数据迁移对象仅仅是所需部分数据的投射而已。
业务对象的属性来自于其映射的实体的属性,业务对象的方法来自于自身的职责以及应用到该实体上的部分业务规则。业务规则在很大程度上是对数据的验证。换句话说,很多业务规则说到底就是验证某个业务对象的当前内容。按照这样的理解,若有专门的验证层,并让业务对象可选择的支持,这个设计将会非常不错。
业务逻辑层不应该看作是一个整体的组件,或是一些不相干模块的组合。多年的实践经验告诉我们,业务逻辑层在其他层中适当的重复是可以接受的,也是很多程序的做法。不过这种做法是有一定的限度,且不应该受到鼓励。
业务逻辑是系统的核心,不过并不是整个系统。业务逻辑设计上的选择将会影响到其他层,特别是持久化和数据访问层,这两层加起来,对项目的成败产生了决定性的影响。
-
添加引用
在业务逻辑层BLL添加对模型层Model、数据访问层接口IDAL、数据访问层工厂类DALFactory的引用。
2.创建基础业务逻辑的抽象类
using OA.DALFactory;
using OA.IDAL;
using System;
using System.Linq;
using System.Linq.Expressions;
namespace OA.BLL
{
public abstract class BaseService<T> where T:class,new()
{
/*使用多态完成子类对父类中当前数据访问类的实例化*/
public abstract void SetDbSession();
public BaseService()
{
SetDbSession();//子类必须要实现抽象方法
}
/*获取当前数据会话类*/
public IDbSession CurrentDbSession
{
get
{
return new DbSession();
}
}
/*获取当前数据访问类*/
public IBaseDal<T> CurrentDal { get; set; }
/*公共的基础操作与DAL保持一致*/
public IQueryable<T> Get(Expression<Func<T, bool>> whereLambda)
{
return CurrentDal.Get(whereLambda);
}
public IQueryable<T> Page<s>(int pageIndex, int pageSize, out int totalCount, Expression<Func<T,bool>> whereLambda, Expression<Func<T,s>> orderByLambda, bool isAsc)
{
return CurrentDal.Page(pageIndex, pageSize, out totalCount, whereLambda, orderByLambda, isAsc);
}
public T Create(T entity)
{
CurrentDal.Create(entity);
CurrentDbSession.SaveChanges();
return entity;
}
public bool Update(T entity)
{
CurrentDal.Update(entity);
return CurrentDbSession.SaveChanges();
}
public bool Delete(T entity)
{
CurrentDal.Delete(entity);
return CurrentDbSession.SaveChanges();
}
}
}
- 创建具体的业务逻辑类并实现抽象父类中的方法
$ vim OA.BLL/UserInfoService.cs
using OA.Model;
namespace OA.BLL
{
public class UserInfoService : BaseService<UserInfo>
{
public override void SetDbSession()
{
//根据当前数据会话类获取当前数据访问类
CurrentDal = this.CurrentDbSession.UserInfoDal;
}
}
}
在业务基类BaseService
中完成数据会话层类DbSession
的调用,将业务层中公共的方法定义在业务基类BaseService
中。公共的方法并不知道通过DbSession来获取那个数据操作类的实例。因此,将该业务基类定义成抽象类Abstract class
,并加上一个抽象方法public abstract void SetDbSession()
和一个IBaseDal的属性public IBaseDal<T> CurrentDal { get; set; }
,并且让基类的构造方法调用抽象方法CurrentDal = this.CurrentDbSession.UserInfoDal
,目的是在表现层new
具体的业务子类时,父类的构造方法被调用,此时执行抽象方法(执行的是子类中具体的实现),业务子类通过DbSession
获取哪个数据操作类的实例。
- 为展示层添加业务逻辑层接口
为业务逻辑层接口IBLL
添加对模型层Model
和数据访问层接口IDAL
的引用。
创建业务逻辑层基类接口
$ vim OA.IBLL/IBaseService.cs
using OA.IDAL;
using System;
using System.Linq;
using System.Linq.Expressions;
namespace OA.IBLL
{
public interface IBaseService<T> where T : class, new()
{
IDbSession CurrentDbSession { get; }
IBaseDal<T> CurrentDal { get; set; }
IQueryable<T> Get(Expression<Func<T, bool>> whereLambda);
IQueryable<T> Page<s>(int pageIndex, int pageSize, out int totalCount, Expression<Func<T, bool>> whereLambda, Expression<Func<T, s>> orderByLambda, bool isAsc);
T Create(T entity);
bool Update(T entity);
bool Delete(T entity);
}
}
创建具体业务逻辑层接口
$ vim OA.IBLL/IUserInfoService.cs
using OA.Model;
namespace OA.IBLL
{
public interface IUserInfoService:IBaseService<UserInfo>
{
}
}
- 为业务逻辑层BLL基类添加引用和接口
在业务逻辑层BLL
中添加对业务逻辑层接口IBLL
的引用
在具体业务逻辑层中添加对应的接口实现,原则是“先继承后实现”。
$ vim OA.BLL/UserInfoService.cs
using OA.IBLL;
using OA.Model;
namespace OA.BLL
{
public class UserInfoService : BaseService<UserInfo>,IUserInfoService
{
public override void SetDbSession()
{
//根据当前数据会话类获取当前数据访问类
CurrentDal = this.CurrentDbSession.UserInfoDal;
}
}
}
业务逻辑层与数据访问层思路是一样的
- 为防止连续多次的实例化DbSession,改造成工厂模式以保证线程内唯一。
在DALFactory
中创建DbSessionFactory
工厂类
$ vim OA.DALFactory/DbSessionFactory.cs
using OA.IDAL;
using System.Runtime.Remoting.Messaging;
namespace OA.DALFactory
{
public class DbSessionFactory
{
public static IDbSession CreateDbSession()
{
IDbSession dbSession = (IDbSession)CallContext.GetData("dbSession");
if (dbSession == null)
{
dbSession = new DbSession();
CallContext.SetData("dbSession", dbSession);
}
return dbSession;
}
}
}
改造BaseService
$ vim OA.BLL/BaseService.cs
using OA.DALFactory;
using OA.IDAL;
using System;
using System.Linq;
using System.Linq.Expressions;
namespace OA.BLL
{
public abstract class BaseService<T> where T:class,new()
{
/*使用多态完成子类对父类中当前数据访问类的实例化*/
public abstract void SetDbSession();
public BaseService()
{
SetDbSession();//子类必须要实现抽象方法
}
/*获取当前数据会话类*/
public IDbSession CurrentDbSession
{
get
{
//return new DbSession();
return DbSessionFactory.CreateDbSession();
}
}
/*获取当前数据访问类*/
public IBaseDal<T> CurrentDal { get; set; }
/*公共的基础操作与DAL保持一致*/
public IQueryable<T> Get(Expression<Func<T, bool>> whereLambda)
{
return CurrentDal.Get(whereLambda);
}
public IQueryable<T> Page<s>(int pageIndex, int pageSize, out int totalCount, Expression<Func<T,bool>> whereLambda, Expression<Func<T,s>> orderByLambda, bool isAsc)
{
return CurrentDal.Page(pageIndex, pageSize, out totalCount, whereLambda, orderByLambda, isAsc);
}
public T Create(T entity)
{
CurrentDal.Create(entity);
CurrentDbSession.SaveChanges();
return entity;
}
public bool Update(T entity)
{
CurrentDal.Update(entity);
return CurrentDbSession.SaveChanges();
}
public bool Delete(T entity)
{
CurrentDal.Delete(entity);
return CurrentDbSession.SaveChanges();
}
}
}
重点
/*获取当前数据会话类*/
public IDbSession CurrentDbSession
{
get
{
//return new DbSession();
return DbSessionFactory.CreateDbSession();
}
}
服务层与门面层
由于展现层中与业务层产生紧密的耦合关系,在分布式的环境中,无法分割部署。因此有必要将展现层与业务层进行解耦,在其间添加服务层或门面层,典型如WebService、WCF、WebAPI等技术。实际上,服务层主要完成的是对业务层实例化的操作,可采用之前的抽象工厂的方式,推荐的方式是使用IoC控制反转的容器来实现。使用第三方的IoC容器,除了能够统一进行实例化对象外还可对其进行依赖注入DI,即对实例化后对象进行一些初始化的操作。在此,推荐的第三方组建如Sprint.Net、Unity等,都是功能强大的控制反转的容器。
服务层
在领域模型模式中,大多将服务层看作业务层的一部分,通常来说,服务层为表现层定义了一个接口,从而允许表现层出发一些预定义的系统操作。服务层可看作是表现层结束,业务逻辑层开始的一个边界,服务层用来尽可能降低表现层和义务逻辑层之间的耦合。让表现层无需关注业务逻辑层中具体实现组织方式。因此,无论采用任何一种业务逻辑模式(表模式、活动记录、领域模型等),系统都可以提供一个服务。
实际上服务层不执行任何具体的工作,其功能在于组织各个业务对象。服务层非常了解业务逻辑(包括工作流、组件、服务),进而也非常了解领域模型。服务层不仅组织业务逻辑,还组织应用程序专有的服务、工作流以及其他任何在业务逻辑层中的特殊组件。
用服务作为表现层和业务层之间的名字是存在争议的,这一层可通过Web服务或WCF服务实现,也可选择其他技术。虽然服务层有服务一词,但要将其理解为一个与技术无关的词汇。
服务层位于系统中两个相互通信的逻辑层之间,使两个层能够在松散耦合并又没彼此离开的同时,仍旧可完美地相互通信。
每个用户驱动的交互的核心都包括两个参与者:表现层的用户界面和服务层实现的用以响应用户操作的模块。也就是说服务层不仅用来i组织业务逻辑,也许要与持久化层进行交互。所有的交互都源自于表现层,并从服务层获取响应,根据接收的输入,服务层将组织业务逻辑层中的组件,包括服务、工作流、领域模型中的对象,并根据需要调用数据访问层。
不仅仅只有服务层会发送数据操作请求,业务还有其他情况,业务逻辑层也可能包含一些工作流或业务服务需要使用的数据访问层。业务逻辑层唯一需要完全和数据库细节分离的部分就是领域模型。
服务从广义上来讲,只要是使用别人的东西那么就是在使用别人提供的服务。在这里,服务是指可能被一个或多个系统使用的核心的业务逻辑,可见其简单的想象成一些可供调用的API。
如何将业务逻辑层提供给其他层来调用呢?
在很多系统中,不是直接将业务层的组件引用就可以的,特别是在分布式的系统中,往往在服务端暴露一些服务接口,让其他子系统或外部系统来调用提供的服务。
一般来说,服务层位于业务层和表现层之间,当前服务层也可以处于系统与系统之间。服务层往往提供一些供外部调用的服务接口。这些接口是一些粗粒度即提供一些简单易用功能强大的接口。当客户端调用接口服务后,服务层就开始处理比较复杂的业务逻辑、验证规则、持久化数据等。
服务层内的逻辑的组织形式类似于Transaction Script模式,可简单地把服务层看作一个中介,从客户端接收请求,通过一系列的步骤后,请求到达服务层,服务层开始协调和组织所需的业务类,把请求的具体处理交给业务类来处理,最后将结果返回给客户端。
在服务层的逻辑组织往往是比较过程化的,与Transaction Script不同的是,Transaction Script的每个方法处理一个比较细小而具体的业务流程和逻辑。而服务层的接口往往处理的是一个较大的流程。
Spring.Net
.NET中创建对象最常用的方式是通过new
实例化创建对象,这样的做法违背了“层与层之间松散耦合”的原则。Spring.Net使用了IoC、DI等概念,提供了一种全新的创建对象的方式。
控制反转IoC,指原来创建对象的权力由程序来控制即new实例化,IoC则改由容器来创建,相当于一个工厂。
依赖注入DI,没有IoC就没有DI,依赖注入指的是容器在创建对象时,通过读取配置文件设置的默认值,使其在创建时就拥有某些注入的值,用以初始化。
Spring.Net是一个依赖注入的设计框架,使项目的层与层之间解耦达到更加灵活。