三层体系结构总结
三层体系的应用程序将业务规则、数据访问、合法性校验等工作放到了中间层进行处理。
开发人员可以将应用的商业逻辑放在中间层应用服务器上,把应用的业务逻辑与用户界面分开。在保证客户端功能的前提下,为用户提供一个简洁的界面。这意味着如果需要修改应用程序代码,只需要对中间层应用服务器进行修改,而不用修改成千上万的客户端应用程序。从而使开发人员可以专注于应用系统核心业务逻辑的分析、设计和开发,简化了应用系统的开发、更新和升级工作。
对于三层体系结构的设计,不同的人有不同的设计方法,我所见过的几个项目中对三层的不同实现
第一种:
三层分别为:DL层,BL层和RL层
DL是数据访问层,其中包含的是单表中的字段属性和对此单表的操作(填查删改),类似Java中的entity Bean的概念。每一个单表对应一个DL
BL是业务逻辑层,其中包含的是业务逻辑,一个BL下引用很多的DL,实现对单表的组合查询及操作,要注意此层涉及到数据库的架构,在设计数据库时,要实现数据表之间是主表与子表的关系。例如:T_Employee雇员表,T_Part部门表,T_Position职位表,T_Site办公地点表
主表是T_Employee雇员表,T_Part部门表,T_Position职位表,T_Site办公地点表为子表
对于表的综合查询方法是:
先对主表查询,调用主表所对应的DL。再根据主表的记录分别对每一个子表进行查询。将自表的查询结果添加的主表后,形成一个大的查询集合。
对于表的操作(增删改)
此时只对主表进行操作,调用主表对应的DL中的操作方法。
RL层是逻辑判断层,主要是对页面上传入的数据进行逻辑判断。RL层之上就是UI
个人感觉此种架构要在数据库设计上注意表之间的关系,尽力满足主与子的关系。在功能上对用户要有一定的限制,不要表现在对于子表的删除操作一定要慎重,以免造成主表与子表的数据在逻辑上出现的主表的外键在子表中没有相对应的值。
第二种我所见过的三层设计模式是:
还是分为UI层、业务层(BLL)、数据访问层(DAL),但其中的数据的存储和传递使用的是Model类,Model类中只有私有字段和公有的属性,并不存在对数据的操作,定义逻辑业务实体,但是实体的定义并不是以单表定义的,而是以一个业务逻辑来定义。
我所遇到的问题是,随着开发的深入,对用户需求的深入,需求在变化,大多是需求膨胀,就某一个逻辑业务实体来说就会不断地膨胀。这样为了实现一个操作有可能要实例化一个很大的实体类,而实际上这个实体类中有用的信息并不多。这样就会造成整体性能的下降。
圣诞节那天和两个朋友(两个漂亮的mm)在上岛咖啡谈论N层架构的实现。他们单位用的是Java,架构是较为严格按照J2EE的模式。当然一共分了七层(我的天!好大的程序)。听完他们的描述,我还是把这七层合并为三层理解(DAL、BLL、UI)。只是实现方式不同。从中也学到了一些东西。
先说UI,Web层中的页面跳转使用的是config文件配置的。例如:当A页面要跳转到B页面时,会执行一些函数或操作得到一个forward的值,根据这个forward的值到相应的Web层的config文件中寻找它应该到哪个页面。用此种方法的好处是使页面跳转十分灵活。这时我想起了我们在作页面跳转时会把代码写到页面的cs或aspx中,如果有几个页面都要跳转到同一个指定页面时,就要在这些页面中写一些代码,如果这个指定的页面名称变了,就要将这几个页面的的跳转代码全部修改一遍。他们这样做的确不错。
再说业务层,说到业务层就要说到业务逻辑,此时不得不涉及到数据库表结构。他们在表结构上不提倡使用外键,一般使用Link关系表作联接。如:Employee表和Department表,在Employee表中不会有DepartID作为外键,而是使用EmployeeDepartLink表作联接,在关系表中只有EmployeeID和DepartID,Employee表中只有Employee的信息。我个人感觉这样做的好处是:在开发的深入时,不会因为Employee关联的信息的增多而造成Employee表的膨胀,且Employee表的架构是固定的。但是我觉得这样做,在查询时信息间联接会多一倍。如:要显示一条人员记录并显示他所在的部门,此时就要三张表集连查询,如果在Employee表中加入外键就可以减少一张表的集连。
再来说说持久层,他们所说的持久层,我理解就是数据访问层。当然这一层中还分了三个小层。有一个业务层和持久层的接口层(叫DAO)。比较吸引我的还是EO这一层。每一个数据表对应一个EO,在一个EO中只有一些公有属性,这些属性就是对应相应表中的字段,我的理解就是在数据库的外面包了一层。
此时就接触到了一个我比较关心的问题,对于业务上集连的查询是如何处理的。得到的结论是:他们大多查询都是对单表的查询。如果有较复杂的查询时直接写Sql语句。晕,我白激动了半天。我所见过的结构中对于这一点的处理要么就是牺牲性能保留架构的完整(正如我在《三层体系结构总结(一)》中写的那样),要么就是牺牲架构得到好的性能,我个人还是倾向保留性能的。当然在页面显示设计时尽力保证所显示的一个数据表中的内容是从一个表中读出的。
不知道我现在的理解是否正确?就我的理解是:由于软件架构的限制,有可能会在设计时就考虑对用户功能的限制以及页面显示的内容及显示的方式,尽量把功能操作分解成对单表的原子操作,尽量不要同时操作多张表。这样也可以减少并发性的问题。
最后,此次谈话中还学习到,大型的项目不建议在数据库中使用大量的存储过程,包括一些网上的资料也表明这一点。后来明白这样做是为了减少服务器的工作量。想想也是,其实一个系统中很大一部分操作是对单表进行的填查删改。
前一段时间帮一个项目组做他们的项目,有幸了解了一下他搭建的架构。相比起以前所见过的架构,我觉得这个应该算是不错的。大体结构如下图:
在编码中体会到这个架构有以下几个优点:
1、 层与层之间依赖于接口:
UI依赖于IBLL,IBLL依赖于IDAL,这样做在设计模式中叫做依赖倒置。也就是说依赖于抽象,而不是具体实现。如果今后的业务逻辑有变动可以不变程序的主体框架,灵活性较好。
2、 使用Castle对类进行管理:
由于层与层之间使用接口连接,但是毕竟要实现多态已实现实际的业务逻辑。使用Castle对类的管理机制,以依赖注入的方式,将接口对应的子类关联起来。这样做可以把变化放到运行时,维护性较好。
3、 Singleton模式的应用
关键的类型因为长期使用,而且其本身占用资源较高,使用Singleton模式,用长期占用空间换取因实例化而造成的时间上的浪费。性能较好。
觉得还存在的问题:
对于复杂业务逻辑处理的还是不太理想:
对于一个事物里有多个操作的问题是这样解决的,在IDAL中有一个方法的入口点,IBLL调用这个入口方法。在DAL扩展这个方法时进行对多个方法业务上的拼装。这样感觉好像把业务层的任务放到了数据层解决。
不过对于这个问题,现在找到了一些解决方法,在VS2003中可以使用TransactionAttribute属性将操作放到一个事务中。在VS2005中有一个Transaction的类(听说的),可以很好的解决这个问题。
下一片文章中打算研究一下这个问题,当然重心放在VS2005上,毕竟VS2003将是过去时,何况TransactionAttribute是COM+中的东西,使用起来觉得不方便。
在这次项目开发中,我们对以前用的三层结构有进行了进一步的改变,除了使用Castle的Windsor容器来管理BLL层和DAL层,在数据的封装和对数据的读取上比以前更加面向对象。
1、 对于BLL层和DAL层的类型,分别继承各自的IBLL和IDAL,使用Windsor容器以注入的方式对其进行实例化,这一点和上次一样,不再赘述。
2、 对于Model层有了一些改进。每一个Model类型会对应一个ModelCollection集合类型,例如:对于订单Order实体会有相对应的OrderCollection实体集合类型。这个集合类型继承自List<T>类型,如下:
public class OrderCollection:List<Order>
//List<T>是一个泛型类型
3、 对于ModelCollection类型中只有一个方法,就是将读取出的数据集合转化为指定Model的对象集合
4、 在Model定义中也和以往简单的数据表字段封装不同,应该说加入了少量的业务逻辑。比如说:
Order的实体类型中定义了OrderDetailCollection,也就是说明Order与OrderDetail是一对多的关系,OrderDetail实体中加入一个Order实体对象
5、 对于日志的实现加入了AOP的思想,使用Castle中的Facility.AspectSharp,对于要加入日志的方法加了一个Attribute标签,在容器中截获方法调用的消息。这样可以减少在系统成型后加入日志带来的修改量
项目中的心得:
1、 对于三层来说UI层、BLL层和DAL层要分别写入相应的代码,在这次项目中感觉我们往往会把业务层中的代码写到其他的层中,比如说:有一个需求是这样的,用户要减少订单中某件物品的采购数量,当用户减少部分数量时,应该对相应记录使用Update操作,当用户减少全部数量时,应该删除相应的记录。我原来对这种判断往往会放在UI层来判断,这次项目中我体会到,对于UI层应该只是调用一种方法,BLL层做判断
UI:Click()
{
BLL.Update();
}
BLL:
Update()
{
If(UpdateFlag)
{
DAL.Update();
}
Else
{
DAL.Delete();
}
}
DAL:
Update();
Delete();
还有,比如用户在界面上的多个类似改变状态的按钮,对于UI层来说应该是多个方法,调用的BLL也应该是多个方法,但是DAL层才应该调用一个方法,而不应该将要改变的状态在UI层就组装好。
UI:
ChangeStateToTrue()
{
BLL.ChangeStateToTrue();
}
ChangeStateToFalse()
{
BLL.ChangeStateToFalse();
}
BLL:
ChangeStateToTrue()
{
ChangeState (True)
}
ChangeStateToFalse()
{
ChangeState (False)
}
DAL:
ChangeState(State)
这些问题也许并不感觉什么严重,但是层次间的功能相对更清晰,也有助于我们进行横向开发
2、 在系统中往往会存在很多的状态,将这些状态提取出来,作为一个单独的项目,可以使用枚举来封装,可以提高代码的可读性,也给今后做这件事的人以方便,而且可以到达统一的效果.
3、 系统中的综合查询部分,对于结果的传递没有使用实体类进行传递,后来感觉综合查询本身就很灵活多变,用实体类去传递结果,显得有些困难。而且结果集本身就是依靠多个实体及实体间关系得出,又如何用一个实体去传递。即使对于每一个查询建立一个实体,对于今后的改动又如何很好的应对呢
项目中的一些遗憾:
开始想使用Castle中的AR建立ORM,但是出了一些问题,应该算是有些遗憾吧。所以后来自己去写实体中的关系,有些地方还是有些牵强,今后要加强学习