分层架构的选择

建立应用程序框架,首先要考虑的问题是,你准备采用哪种分层架构,然后根据应用程序框架的逻辑层次来确定需要创建的VS解决方案和程序集。

如果项目很小,需求很简单,时间异常紧迫,且你手上没有任何积累,那么,单层架构将是首选,最简单的单层架构如下图所示(为了集中你的注意力,我把不相关的文件都删除了)。

单层架构的主要优势是代码火力集中,干活直截了当,不像多层架构那样拐弯抹角,每个操作都需要层层传递。对于上图的User.aspx,所有相关代码都直接写到aspx页面或后置代码中,包括界面上的控件操作,业务操作和数据库操作。

另外,单层架构比较符合初学者的习惯,初学者对编程语法和.Net 基本API尚且不熟,哪会过多考虑代码结构上的问题。

使用单层架构,主要依赖于.Net强大的控件体系,拖控件,配属性,写事件处理函数,是单层架构下编程的真实写照,这也是.Net程序员在外人眼里一直保持的印象。

对于主要包含CRUD操作的小项目,单层架构可以工作得很好,但如果项目有一些比较复杂的业务逻辑,比如订单流程 ,会发现代码很快会变成一团烂泥,失去方向。业务逻辑与表现层代码混杂在一起,其结果是界面上的任何修改都可能导致业务逻辑失败。

为了提升代码复用能力,可以把每个页面都需要处理的操作,比如权限检查,日志跟踪,全局错误处理等,提取到一个PageBase的基类中,放到Base文件夹,另外可以创建一个Helpers文件夹来放置公共操作类。

单层架构为下一个类似项目所作出的积累微乎其微,业务上大部分代码会被抛弃,有可能是技术的变化,比如之前使用Web Form,现在需要使用Win Form, 由于业务代码和表现层代码高度耦合,很明显无法再使用之前的代码。哪怕技术没有变化,要在之前开发的烂泥中新增和修改功能,将是举步维艰。有些逻辑上看似非常简单的需求,当你实际动手时,发现并不是这么简单,修改一处会牵动更多地方,所谓牵一发而动全身。

对于技术上的积累,主要通过复制文件的方式供下一个项目使用。一个更好的办法是把技术积累单独提取到一个.Net类库中,如下图所示。

这样就实现了业务与技术的分离,当下一个项目到来时,只需要把Util.dll引入即可。

为了挖掘单层架构的极限能力,需要开发一套强大的自定义控件,把数据库操作、验证操作、权限操作等内容全部封装进去,在控件上设置数据库列名,不需要经过DataTable或实体对象,直接生成Sql发送到数据库。如果配上代码生成器,开发简单项目的速度惊人。

对于单层架构,不论你怎么想尽办法,它对复杂项目都无能为力,使用单层架构来开发业务逻辑复杂的项目,最终得到的就是一团乱麻。提升代码质量可以在一定程度上缓解这些问题,但要从根本上解决,还必须向多层架构靠拢。

从上面分析可以看到,单层架构不能胜任复杂业务领域,而且复用能力十分有限。由于应用程序框架的目标在于提升技术和业务两方面的代码复用,所以单层架构不是一个合适的选择。

信息系统从根本上来说,是对数据库的操作,并且需要提供一套操作界面。如果按相关职责划分到不同的层中,会有助于项目的管理和代码清晰度的提升。分层架构是SRP(单一职责原则)的一个应用,把高度相关的东西聚集在一起,把各个层用接口连接起来,这样就能得到一个高内聚、低耦合的设计。

目前.Net程序员普遍使用的分层架构是传统三层架构,架构示意图如下。

传统三层架构的核心在业务逻辑层,表现层会把数据放到实体中,作为参数传递给业务逻辑层,业务逻辑层会使用过程式的代码来处理业务,如果需要访问数据库,则调用数据访问层。

传统三层架构把表现、业务和数据访问分离到不同的层中,当切换表现层技术,或更换数据库时,只需要替换相关的组件即可,不需全部重写。

天下没有白吃的午餐,在获得清晰度、重用性,扩展性等好处的同时,也带来了一些问题,主要是直观性降低,复杂度提升,工作量增大。在单层架构下实现一个功能,可能只需要一个方法就能完全搞定,理解起来也比较容易,进入三层架构以后,每个操作都被分离打散到不同的层里,每个层有一部分代码,需要在几个层里来回跳动,才能窥其全貌,所以直观性有所降低。另外,任何一个操作,需要在多个地方编写,工作量倍增。

如果严格按照三层架构的要求来编写代码,表现层尽量不要有业务操作,更不能有数据操作,业务层只管逻辑,这样可以获得比较高的可维护性。但是很多初学者对架构职责没有认识,他们会随便找个地方放置代码,这个地方多半是表现层,这样一来,不仅代码质量很差,架构还很复杂,就得不偿失了。

传统三层架构特别具有争议的地方是Model实体层。这些实体从表面上看,好像是业务对象,比如客户Customer,仔细观察这个类,发现里面全是属性,没有方法,它用来充当数据容器,在各层之间传递数据。

有些人一直认为自己是在进行面向对象开发,毕竟c#是个面向对象的语言,又使用了三层架构,还创建了实体,不就是面向对象开发吗?实际上传统三层架构还是面向过程的,只是披上了面向对象的外衣。还有些人对是否面向对象开发不屑一顾,“管它这些理论干啥,咱只搞实际的”,没错,一般程序员确实可以不管这些道理,但如果要追求更高的封装性、复用性,可维护性,就必须向面向对象深入。

面向对象的核心是根据概念建模,比如客户,虽然传统三层架构在Model层中确实创建了一个Customer类,但是Model层在三层架构中属于辅助地位,是一个可有可无的东西,如果把Customer类换成DataTable也一样可行。

Model中的实体之所以用处不大,是因为违反了对象的特征,真正的对象是数据与操作的集合。有人把单纯的数据对象称为贫血模型,把具有数据和丰富操作的对象称为充血模型,意指只有属性的数据对象先天不足,营养不良,智商低,只会吃饭(数据),不会干活。

为何对象一定要把数据和操作放到一起,才能发挥威力?因为这些数据与数据上的操作总是息息相关,且同时变化。把数据和操作封装到一起,可以为操作提供唯一访问点,主要好处是对操作集中管理,消除代码冗余。当数据发生变化时,相关操作多半需要同时修改,由于代码没有冗余副本,且在对象内部完成修改,对外界甚至不产生影响,可维护性大大提升。

把业务对象的数据和操作分离以后,第一个影响是业务逻辑散乱,难以管理。由于操作没有一个统一的位置,所以没有人知道这些操作究竟位于何处?可能在业务层,也可能在表现层,还有可能在存储过程里。哪怕严格按照三层架构职责编写代码,所有业务逻辑都在业务层中,但可能很多个类都使用了这个实体,究竟某个操作在哪个类里?特别是在团队作战时,这个问题更加明显,如果他不能很容易找到他要的方法,那么他就会自己添加一个,从而导致冗余。第二个影响就是导致明显的代码冗余,而代码冗余是可维护性的天敌。每当数据发生变化或发现Bug,需要找到所有操作代码的副本进行修改,如果有遗漏就会埋下地雷。

当然,CRUD简单操作不在以上讨论范围,上面主要指业务逻辑受数据的影响程度。

可以看到,三层架构相比单层架构来说,已经有相当进步,但它还有一些缺陷可以改进,如果把Model实体层和BLL业务逻辑层合并,就可以得到面向对象模型,在《企业应用架构模式》中称为领域模型。不过Eric Evans在《领域驱动设计》中以更具体的方式指导如何使用面向对象进行开发。

应该采用充血模型(面向对象)、还是贫血模型(面向过程)?要不要使用ORM,要不要使用DDD(领域驱动设计)?这些问题总是争论不休。不过我的建议是,如果你还准备在.Net界多混几年,不如提早进行,因为这是大势所趋,就好像单层架构最终被三层架构所取代。另外,领域驱动设计如果用得不好,充其量也就和三层架构差不多,所以你不需要有任何顾忌。

本系列文章将采用Entity Framework和DDD分层架构演示应用程序框架的建设,所以本文暂不对DDD进行介绍。

本文对分层架构的演化过程进行了简单介绍,为你选择适合自己的架构提供了一些参考,最后的结论是,无论你是否愿意,由于软件行业的发展,特别是微软技术的推动,你始终会走上面向对象之路,与其被动挨打,不如主动学习。

有些朋友发现这个实战系列名不符实,全是废话,一句代码都没有,还有些朋友喜欢四处搜集源码,坐等干货。我想说的是,这个实战系列毕竟是介绍架构和框架的,如果你只拿到几行代码,没有真正搞懂如何为你的项目建立应用程序框架,代码无法形成一个体系结构,还是一团乱麻,那又有什么用。另外,我所提供的代码也是四处搜集整理,没有什么特别,你如果需要源码,直接百度就会多如牛毛,但我希望你能搞懂每个细节, 这样更有帮助。

应用程序框架实战