一、上章回顾
上章我们主要讲述了系统设计规范与原则中的具体原则与规范及如何实现满足规范的设计,我们也讲述了通过分离功能点的方式来实现,而在软件开发过程中的具体实现方式简单的分为面向过程与面向对象的开发方式,而目前更多的是面向对象的开发设计方式。并且我们也讲述了该如何通过设计手段去分析功能点及设计分离点,应该如何在设计的过程中分析的角度及如何去满足设计规范与原则。首先我们通过下图来回顾下上章要点:
二、摘要
本文将已架构的方式去分析分层结构中的业务层的设计,如何写出来内聚度,高耦合的业务逻辑层,并且如何根据我们的项目中的个功能需要去设计业务层。我们本章将会通过几种可能的业务层的设计模式去分析,并且分析每种设计模式的优点与缺点及每种设计模式的应用场景,并且结合一定的使用实例来讲解,当然这些具体的内容都是自己在项目中的经验与总结。错误之处,在所难免,本人抱着求真务实的态度,争取写好每一篇文章,错误之处还请大家匹配之处,如果您有好的意见或建议,可以及时跟我沟通,如果对系统架构中的设计规范与模式不了解,可以点击这里查看上篇文章:系统架构师-基础到企业应用架构-系统设计规范与原则[下篇]
本章讲解的内容我们先看下图中的几个简单模式:
三、本章大纲
1、上章回顾。
2、摘要。
3、本章大纲。
4、业务层设计分析。
5、几种不同的业务设计模式。
6、本章总结。
7、系列进度。
8、下篇预告。
四、业务层设计分析
本节中将会对业务层的设计进行详细的分析。
首先、我们知道业务层是一个系统中最核心的部分,业务层是实现系统业务功能的核心逻辑层。业务层我们通常说的BLL(Business Logic Layer)层,现在我们一般的稍微复杂一些的业务逻辑都是通过分层结构来构建一个应用系统,我想大家在组织业务逻辑功能时大部分的情况下是使用BLL层单独负责相应的业务逻辑来实现的。有些应用可能业务逻辑层并不复杂,这是我们可以把问题简单化,不用引入一些框架性的东西来提升系统的复杂度,但是有些业务规模较大,并且业务逻辑性较强时,可能使用好的业务设计模式带来的优越性就显而易见了。 我们大家都知道业务逻辑层主要是用来处理领域模型对象之间的逻辑关系的部分。我们都知道业务层的数据最终是要保存到数据库中,我们在进行业务层设计时一般是在架构中的分层架构模式中出现的。我们知道分层结构中一般是将领域模型与底层数据访问、表现层等进行分开组织,这样可以让系统结构上清晰,并且容易降低他们之间的耦合性。
其次、其实我们的很多操作都是可以在业务层来完成,比如说用户的角色权限,数据验证等一些基本的业务规则。当然这里说明业务层主要负责系统中的业务规则的实现。这里我们需要知道2个概念,就是对象模型与领域模型:下面我们说下这2者的区别。
描述对象模型与领域模型的区别。
最后、业务逻辑层作为分层系统中的中间位置,业务模型是表现层与数据层之间的纽带。当然一般我们在系统设计时,可能我们一般不会把领域模型中的领域实体作为分层之间的传输信息,因为一般来说领域模型中的实体不但包含实体的数据信息,并且包含实体的行为。可能我们在各层中只会用到实体的数据信息,那么无疑这时采用领域实体的形式进行传输,那么会增加系统的传输负载。当然这里就会出现我们平时所谓的3层模式中的Model层。当然Model层设计的主要作用就是实体数据的承载,其中并不包含任何行为。具体的行为通过数据访问层来实现CRUD(DDL中的四个基本操作)的操作。
目前我们设计的分层结构中的对象模型中并不包含实体的行为。其实这里可以看作是比较好的设计方式,因为采用对象模型的方式进行数据传输时可以降低系统的耦合。当然我们也可以把领域实体看作是多个对象实体的组合并且包含这些对象实体之间的关系。所以我们在做系统架构时可能如何权衡就比较重要,具体是使用领域实体进行数据传输还是对象实体主要还是看业务的需要。
五、几种不同的业务设计模式
首先我们在业务逻辑层设计时,必须首先确定是采用面向过程还是面向对象的模式来设计。 下面我们就将针对开篇介绍的几种模式进行分别介绍,错误之处请大家批评指出。我们首先来讲解过程式的2类逻辑层的架构模式。
过程式模式
1、事务脚本模式
事务脚本模式是面向过程的模式,那么我们就不会采用面向对象的设计思想。这种模式是将业务代码映射到用户的操作中。简单的理解就是将用户的每个操作都对应一个方法,那么这个方法就是我们这里讲的事务脚本。 当然这里的“事务”就是指我们平时说的业务流程,“脚本”则是我们说的流程中的相应操作方法。这里
需要注意的是 ,不要认为数据库操作的相应方法也属于脚本中的内容,我们通常还是将数据访问层单独的书写,只不过脚本中调用而已。通常,事务脚本模式中都是一系列的逻辑判定或者循环或其他方式,通过一系列的函数调用来实现业务流程的。通常来说一般业务比较单一或者不是很复杂的系统功能,通过该模式实现起来会比较简单。而且如果业务流程中的变化较为频繁的话不建议使用该模式来做。我们认为,我们对于这类简单的功能,我们没有必要花费较大的代价去设计领域模型和其他方面的考虑。事务脚本模式的一大优势就是很简单,没有开始时的额外代价,它可以很好的进行快速的应用程序开发,一般情况下来说,如果一个项目的时间紧迫,逻辑简单,并且使用强大的开发工具时,我们可以使用这样的模式来进行,无论是成本还是开发效率上都非常客观。
我们根据上面的介绍可能认为,事务脚本模式的可重用性不高,并且耦合性太高,适应性并不好,当时我们仍然可以通过我们好的设计来实现代码的重用,我们可以在进行“脚本”编写时将重用的部分抽象出来,写一个单独的函数,以达到复用的目的。事务脚本模式的一个重大缺点就是通过这样的设计很容易造成代码重复,通常来说我们很容易完成一系列的业务功能,但是随着应用程序功能的增加,那么应用程序代码会变成非常复杂的程序结构,当然我们可以通过重构去环节该模式的这一劣势,当规模达到一定程度时,我们同样没办法去完成重构工作。
通过上面的讲述,我们对事务脚本模式有了初步的认识,那么下来我们看看我们在进行业务逻辑层设计时的详细使用该模式的步骤及相关准则。
本图描述了事务脚本模式的设计过程,那么基本上每个系统流程都可以通过这样的流程来完成设计。下面我们就针对一个简单的实例来讲解下
如何通过这样的设计流程来实现系统的功能。
我们这里以简单的购物流程来讲述。如何下一个订单
首先、我们先来分析下用例:
我们知道注册的会员可以通过将产品添加到购物车中,然后通过去付款模块来进入支付系统,完成订单。
其次、分析出事务。
一般来说购物的流程是这样的流程。当然这里也不是标准的形式。
那么我们如何上述的几个步骤的内容,去完成这个事务的流程。
publicclassBuyInfo { ///<summary> ///购物车中的产品列表 ///</summary> privateList<Product>proList=newList<Product>(); publicintCreateBuyCar(Productproduct) { //事务开始 this.BeginTranstion(); //判定当前添加到购物车中的产品信息是否是自己发布的产品 if(!this.IsCanBuy(product.PutOutID)) return; //判定当前产品的库存信息是否大于当前购买的数量; if(!this.IsLagerThenBuy(product.PutOutID)) return; //添加产品到购物车 proList.Add(product); //处理生成订单信息 intorderID=this.CreateOrder(); //事务结束 this.EndTranstion(); returnorderID; } ///<summary> ///生成订单 ///</summary> ///<returns></returns> privateintCreateOrder() { Orderorder=newOrder(this); returnorder.CreateOrder(); } ///<summary> ///判定当前产品的库存信息是否大于当前购买的数量 ///</summary> ///<paramname="p"></param> ///<returns></returns> privateboolIsLagerThenBuy(intp) { returnfalse; } ///<summary> ///判定是否是自己发布的产品信息 ///</summary> ///<paramname="p"></param> privateboolIsCanBuy(intp) { returnfalse; } privatevoidEndTranstion() { //TODO.. } privatevoidBeginTranstion() { //TODO.. } }
这里定义的是添加到购物车的流程,下面将给出如何将购物车中的产品信息添加到订单中的具体流程。
publicclassOrder { privateBuyInfoinfo; publicOrder(BuyInfobuyInfo) { info=buyInfo; } publicintCreateOrder() { //将BuyInfo购物车中的产品列表与订单信息进行关联。 this.InitOrderInfo(); //将订单中的会员信息关联 this.InitOrderUserInfo(); //将订单中的收货信息关联。 this.InitOrderReciveInfo(); //将订单中的支付信息关联。 this.InitOrderPayInfo(); //生成订单。 } ///<summary> ///将购物车中的产品添加到数据库中 ///</summary> privatevoidInitOrderInfo() { thrownewNotImplementedException(); } ///<summary> ///初始化订单的支付信息 ///</summary> privatevoidInitOrderPayInfo() { thrownewNotImplementedException(); } ///<summary> ///初始化订单中的收货信息 ///</summary> privatevoidInitOrderReciveInfo() { thrownewNotImplementedException(); } ///<summary> ///初始化订单的买家信息 ///</summary> privatevoidInitOrderUserInfo() { thrownewNotImplementedException(); } }
通过上面的形式基本上就完成了简单事务脚本模式的构建,当然我们也可以将所有的一系列的脚本封装到一个类里面,通过静态方法的方式来访问也是可以的。只是组织方法的形式不同。当然我这里提高了,通过将脚本封装到一个类里面,通过静态方法的形式或者实例方法的形式都是可以的,那么我们来讲讲什么时候封装成静态方法,什么时候封装成实例方法。
我们如何将数据传入给实体脚本呢,通常我们是通过前面我们说的对象实体来完成的,因为数据对象实体只是包含实体的数据信息,并不包含具体的行为。
那么我们来简单说说对象实体中的信息及格式,我想大家用过分层结构中的Model层的都会比较的清楚,下面我们看看,不详细介绍了:
publicclassProduct { privateintputoutID; publicintPutOutID { get{ returnthis.putoutID; } set{ this.putoutID=value; } } privateintproductID; publicintProductID { get{ returnthis.productID; } set{ this.productID=value; } } privateintproductName; publicintProductName { get{ returnthis.productName; } set{ this.productName=value; } } privateintproductUnit; publicintProductUnit { get{ returnthis.productUnit; } set{ this.productUnit=value; } } privateintproductClass; publicintProductClass { get{ returnthis.productClass; } set{ this.productClass=value; } } }
我们看到了主要是通过get;set访问器来实现的。
总结:业务逻辑层是将表现层触发一系列事务分别实现,那么对业务逻辑层的建模其实就是对事务的具体实现,将事务映射到业务逻辑层中的不同方法上,一般来说事务代码维护起来比较难以维护,重用性低,难以理解。
2、表模块模式
表模块模式是对事务脚本模式的实践总结并优化而提出的新模式。相比而言,表模块模式结构性更强,总体来说,表模块就是就是将数据库中的某个表上的所有可能的操作都写在一个类文件中,这个类文件中定义了该数据库表对应的所有的业务方法。表模块类是一个容器,它内部包含了类的数据信息和相应的行为。表模块是映射数据库表的关系,因此表模块对应的是数据集合,那么无法将数据库表中的某个行记录进行区分,只能通过集合中的键或者索引来访问行记录的信息。
表模块模式由于是在事务脚本模式发展的更有结构化的模式,那么可以说表模块模式虽然是面向过程的模式,但是它朝面向对象的模式已经迈了很大的一步,我们知道,在将对象模型中的数据保存到关系数据库中时,需要使用相关的ORM工具去完成相应的转换。我们知道面向对象模型能够很灵活、更好的对领域模型建模。当然,表模块模式仍然关注的是方法,而不是对象。
通常我们在更好的结构设计指导的同时,那么我们需要考虑更多的规则,那么意味着我们需要书写更多的代码。表模块模式的具体实例可以参考Visio Studio中的提供的DataSet和dataTable。表模块在处理简单的实体时具有很好的优势,但是当实体较复杂时,或者实体之间的关系紧密时无法很好的处理。下面我们来看看一个简单的例子来说明表模式的使用。我们这里还是以刚才事务模型中的购买流程来讲解。
数据库表与表模块类的映射关系是通过.NET提供的内存数据对象DataSet、
DataTable来实现。
publicclassOrder { privateSystem.Data.DataTable_orderItems; privateDataSet_ds; publicOrder(DataSetds) { _ds=ds; } ///<summary> ///返回订单对应的所有产品信息 ///</summary> ///<returns></returns> publicDataTableProducts() { return_orderItems; } ///<summary> ///返回当前索引号的订单信息 ///</summary> ///<paramname="index"></param> ///<returns></returns> publicDataRowGetRow(intindex) { return_ds.Tables[0].Rows[index]; } ///<summary> ///根据订单的ID返回订单的信息 ///</summary> ///<paramname="id"></param> ///<returns></returns> publicDataRowGetRowByID(intid) { return_ds.Tables[0].Select("id="+id.ToString()); } publicintUpdate(intorderID) { return0; } publicintDelete(intorderID) { return0; } publicintInsert(intorderID) { return0; } }
我们这里的数据如果发生变化时,我们如何将变化了的内存中的datatable或者dataset持久化到数据库中呢,为我们提供了dataadapter,通过这个适配器我们完成datatable中的数据持久化到数据库表中。具体的形式就是上图中的流程,UI层通过数据请求方法,然后表模块层中有相应的方法,将发送数据请求访问数据库,然后数据库返回相应结果通过datatable或dataset来初始化表模块类中的数据信息,然后返回给UI层相应的数据信息,然后绑定后显示。
总结:表模块模式相比事务脚本模式。是我们推荐的使用模式。并且VS中自动继承了相应的datatable 与datas的相应的图形化设计方法,很方便的使用。并且内部.NET framework 内置了相应的操作方法可以迅速的实现交互,在过程式模式中无疑是使用表模块模式是首选方案。表模块模式更关注的是与数据库表的映射关系,那么可以说表模块模式中包含了数据模型。有一些面向对象的味道,不过表模块模式中并不关注业务。
对象式模式
我们来讲述下面向对象的模式中的2种方案
1、活动记录模式
我们知道业务逻辑层中的业务逻辑才是系统的核心,那么我们更关注的是业务逻辑或者领域逻辑。因为我们必须关系实体之间的交互。我们如果对领域模型建模。我们需要使用面向对象的角度去分析需求。 我们先来看看基于对象模式的活动记录模式。
活动记录模式是在表模块模式的基础上,将表模块的粗粒度的基于数据集上的映射细化到表中行记录的细粒度的映射,并且映射的对象中包含相应的数据库表中的数据信息,并且包含对象的行为。例如,我们现在如果想将产品表中的产品信息映射到数据库中的表product中的行记录,那么我们需要构建一个product类,该类中的属性与product表中的列信息一一对应。并且product类中还包含该对象所有的行为。例如CRUD的操作,其他的行为等。
首先我们需要知道活动记录的优势是什么?首先我的理解就是活动记录模式容器理解,简单,并且目前有很多的框架支持这样的模式。首先就像Linq、Castle框架都是采用这样的模式。。一般情况下,活动记录模式在关系型模型中可以很好的支持。如果在应用程序中不使用关系型模型来设计业务层的话,那么需要我们人工来组织,协调活动记录与数据模型之间,其实这个就可以简单的理解为数据映射,一般的ORM框架中都提供这样的功能。
我们需要知道,如果活动记录模式下的对象发生改变了,那么数据库也得跟着修改,或者数据库表发生改变了,那么你不得不修改对应的活动记录对象及相关代码。
总而言之,如果我们在使用活动记录模式的过程中如果不能保证对象模型与数据模型之间的对应关系,那么我们将会很快失控,那么领域模型显然能够解决这样的问题。下面我们来看下活动记录模式的实例。我们还是以购物流程为例。
publicclassOrder { privateint_orderID; publicOrder(intorderID) { _orderID=orderID; } publicintOrderID { get{ returnthis._orderID; } set{ this._orderID=value; } } privateint_orderState; publicintOrderState { get{ returnthis._orderState; } set{ this._orderState=value; } } ///<summary> ///返回订单对应的所有产品信息 ///</summary> ///<returns></returns> publicList<Product>Products() { returnthis.GetProducts(); } privateList<Product>GetProducts() { List<Product>lists=newList<Product>(); returnlists; } publicintUpdate() { return0; } publicintDelete() { return0; } publicintInsert() { return0; } }
我们可以看到区别,从原来的表模式中的通过提供的内存集合表中的形式,修改为通过对象中的属性的形式来保存对象的数据信息,显然这就是表模式与活动记录模式的最大区别。不过实现的思想上差别不大。
我们都知道数据库表之间如果有关联的话,那么我们通过的是外键的形式来进行关联,那么我们在活动记录中一般如何来标识某个类对应的外键对象信息呢。
那么我们在订单中可能会有对应的用户ID,不过在不同的框架中可能对外键对应的对象信息的存储方式会有所不同,有些框架中在外键对象上通过属性的方式返回对象的实例的应用。简单来说下这2种方式的区别:例如:
publicclassOrder { privateint_orderID; publicOrder(intorderID) { _orderID=orderID; } publicintOrderID { get{ returnthis._orderID; } set{ this._orderID=value; } } privateint_orderState; publicintOrderState { get{ returnthis._orderState; } set{ this._orderState=value; } } privateint_userID; publicintUserID { get{ returnthis._userID; } set{ this._userID=value; } } }
另外一种形式。直接将User用户的完整对象引用实例返回。
publicclassOrder { privateint_orderID; publicOrder(intorderID) { _orderID=orderID; } publicintOrderID { get{ returnthis._orderID; } set{ this._orderID=value; } } privateint_orderState; publicintOrderState { get{ returnthis._orderState; } set{ this._orderState=value; } } privateUserInfo_userinfo; publicUserInfoUser { get{ returnthis._userinfo; } set{ this._userinfo=value; } } }
上面我们看到了活动记录模式的简单使用方式,当然我这里没有说是写出来可以运行的实例代码。只是简单的显示了,使用这些模式可能的用法,当然如果大家有更好的理解或者用法可以一起交流。当然活动记录模型一般就能满足我们的系统功能,如果说针对业务复杂的领域,那么必须设计领域模型去完成相应的业务逻辑层的设计。下面我们将来介绍领域模型模式的相关实例。
2、领域模型模式
我们前面讲解的几类模式,其实说白了都是以数据为中心进行抽象与设计的形式,当然采用这样的形式无疑是以业务中的数据为中心,并不是关心的业务本身。以数据为核心的设计方法一般流程如下:
一般来说我们目前主流的方式都是这样的顺序来执行。当然通过这样的方式的确一般的简单应用当然都是没有太大的问题。有的时候我们需要同时关系对象的数据与行为,那么显然以数据为核心的情况就会显得力不从心,当然简单的系统时不会有太大的体现,当时当系统的规模上升到一定的程度时,哪怕添加一些新的需求时,对系统的影响都是致命的。
领域模型关注的是系统期待的行为及系统正常工作所需要的数据流。一般我们在设计领域模型的时候需要这个领域的专家协助,最后以类的形式呈现出来。当然
领域模型模式中的实现方式,大家都知道的就是有名的DDD(领域驱动设计)。
领域模型设计的最终目标就是与系统的概念模型匹配起来。我们可以把领域模型看作是概念模型的物理模型。在一个项目中可以说领域模型是最重要的,最关键的部分。
领域模型可以看作是一系列对象之间的交互。领域模型中的每一部分都需要用一个带有行为的类来表示。这个怎么理解呢,请看图:
订单信息对象首先会与订单中的产品对象有交互关系,产品项与产品分类有交互关系。订单信息与用户买家有交互关系,订单的其他信息(物流信息、支付信息等)有交互关系。
例如我们常说的记录的系统日志,不管是错误日志还是其他的所有系统中的日志,那么我们建议的方式采用AOP的方式来处理。那么我们来看看具体的代码实现:
//定义接口 publicinterfaceILog { //具体的写日志的方法 voidWriteLog(); } //简单实现 publicclassBaseWrite:ILog { #regionILog成员 ///<summary> ///基本的书写日志的方法 ///</summary> publicvoidWriteLog() { try{ Write.WriteLog(this); } catch{ } } #endregion }
具体调用的写日志的方法:
publicclassWrite { publicstaticboolWriteLog(ILoglog) { //具体的写如日志的方法。 returntrue; } } 具体相关的产品类与产品分类对象的使用: ///<summary> ///产品类 ///</summary> publicclassProduct:BaseWrite { //产品的相关属性 publicintProductID{get;set;} publicintProductName{get;set;} publicintProductClassID{get;set;} publicintProductSort{get;set;} publicintProductIsEnable{get;set;} //产品类相关的方法 publicList<Product>GetAll() { returnnewList<Product>(); } } publicclassProductClass:BaseWrite { //产品分类相关属性 publicintProductClassID{get;set;} publicintProductClassName{get;set;} publicintProductClassCategory{get;set;} publicintProductClassSort{get;set;} publicintProductClassIsEnable{get;set;} //产品分类相关的信息 publicList<ProductClass>GetAll() { returnnewList<ProductClass>(); } }
我们看到了在活动记录模式中会将数据访问层的代码写在该层,但是领域模型中一般不会出现这样的代码。领域模型中只关心跟业务流程相关的内容,具体的数据持久化之类的内容一概不关心。领域模型中是一系列的对象之间的交互,那么我们可能会关心,如何将领域模型中的这些对象的状态进行保存呢,那么就会牵扯到对象模型到关系型数据库中持久化的个问题了,在持久化的问题上,我们可能有几种选择,一种是采用现有的ORM框架去实现对象数据的自动持久化,当然这是一种解决办法。有时候我们是通过手动的方式来进行持久化,可能就不存在这样的问题。
我们通常在持久化数据的问题上我们希望能做到持久化的透明性,这样就可以说具体的业务层不需要书写持久化的操作代码,当然我们现在设计的时候可能都是要么是通过特性来实现持久化,或者是通过继承基类的方式来做持久化的操作,那么可能带来如下的问题。
一般我们知道,一个类如果想不写相应的持久化代码还能实现持久化的话,那么我们只能通过动态的注入持久化代码的方式来实现这样的持久化透明的方案,微软的EntityFrameWork2.0中的POCO中的ORM方案,就是通过这样的方式来实现的。我们更关心的是代码应该放在何处,是在领域模型中还是领域模型外呢,当然目前的一些常见的框架中已有实现方案,其中大家熟知的NHibernate就是持久化透明的方式,大家如果感兴趣可以参考NHibernate的实现思路来做。一般对于这种动态注入代码的这样的方案,这里简单介绍下可能的实现,由于.NET framework 中提供了一个派生自特性类型的代理类,通过代理类来动态的将这个类的相关功能追加到指定的对象中,这就是所谓的动态代理,当然针对某个特定的类型这样来做,WCF也是通过代理类的方式来实现这样的动态插入的形式。
六、本章总结
本章主要是对业务层建模的不同的架构模式进行了相应的总结及简单说明,可能某些地方说明不是特别的清晰,当然我也是将平时自己的业务开发中的一些经验和总结,部分内容是参考以前看的书上的内容,不足之处还请大家多多的指点,还有请感兴趣的朋友一起交流领域模型的设计,包括持久化的透明的实现方案等,欢迎大家多提意见和建议。本人抱着学习和宁缺毋滥的原则,写好每一篇,可能看过这方面资料的同仁当温习下相关内容,没有看过同仁希望对你们有帮助,那就是我最大的动力。
八、下篇预告
下一篇我们将会开始讲解软件设计中最重要也是最基本的技能-设计模式,希望大家多多提出已经,后面3篇我们将会讲解如何在项目中使用设计模式及使用设计
模式需要注意的事项,将会举例说明每个设计模式可能出现的场景。希望大家持续关注!
作者:CallHot-何戈洲
出处:/hegezhou_hot/
如果觉得《系统架构师谈企业应用架构之业务逻辑层》对你有帮助,请点赞、收藏,并留下你的观点哦!