.Net企业级应用架构设计之业务层设计
业务层剖析
任何复杂的任何软件都可以通过层来组织,每一层表示系统中的一个逻辑部分,一般来说,业务逻辑层中的模块包含了系统所需要的所有功能上的算法和计算过程,并于数据层和表现层交互。抽象的说,业务逻辑层是软件中专门处理业务相关任务性能的部分。
业务逻辑层表示了系统的逻辑,此处的代码将要进行必要的决断并执行操作。前面谈到过安全性,在业务逻辑层的安全性意味着使用基于角色的安全原则,仅允许认证用户访问特定的业务对象。从外界看,业务逻辑层可以看作是一个操作业务对象的机制,一般来说,业务对象不过是某个领域实体的实现,或者是某类辅助类型,用来执行一些计算。业务逻辑层处于分层系统的中间位置,业务逻辑层的输入和输出不一定是业务对象,很多时候,架构师更倾向于使用数据迁移对象在层之间交换数据。
数据迁移对象和业务对象之间的取舍一直是团队中争议的话题。建议使用数据迁移对象的理论认为,数据迁移对象能减少层之间的耦合,使系统更加整洁干净。不过在现实中,人们都会说复杂性已经很高,因此应该避免增加任何不必要的对象。一条实用的原则是,当已经有了数百个业务对象时,或许并不应该仅仅为了设计的干净而让这个数字加倍,在这种情况下,数据迁移对象通常就是业务对象。业务对象同时包含了数据和行为,是一个可以参与到领域逻辑的完整对象。而数据迁移对象更像是一种值,即一系列数据的容器而没有相关的行为。为了序列化,业务对象中的数据会复制到数据迁移对象中。除了get/set访问器之外,数据迁移对象没有逻辑行为。数据迁移对象并不仅仅是领域对象去掉了行为,它表示了特定领域对象的一个子集,用于专门的上下文中。一般来说领域对象是一个对象图,而数据迁移对象仅仅是所需要部分数据的投影而已。
每个真实的世界组织都是基于一系列业务规则运行的。或许很多人没有认识到组织中存在的这些规则,但不能否认这些规则的存在。每个组织都有自己要实现的战略,而业务规则是保证实现这些战略的主要手段。战略决定了组织的发展方向,而业务给出了实现战略的具体做法。
业务对象的属性来自于其映射的实体的属性,业务对象的方法来自于其自身的职责以及应用到该实体上的部分业务规则。业务规则在很多程度上是对数据的验证。换句话说,很多业务规则说到底就是验证某个业务对象的当前内容。按照这样的理解,若有专门的验证层,并让业务对象可选择的支持,这个设计将会非常不错。
业务逻辑层不应该看作是一个整体的组件,或者一些不相关模块的组合。多年的实际经验告诉我们,业务逻辑层在其他层(经典三层)中适当的重复是可以接受的,也是很多程序的做法。不过这种做法有一定的限度,且不应该受到鼓励。
业务层和其他层
假设我们有一个经典的分层系统,分为表现层、业务层和数据访问层,那么业务逻辑分布在各个层中的百分比应该是多少?最权威的答案是0 - 100 - 0 。这个数字显然不现实,通常你应该尝试把所有的事情放在业务逻辑层完成,除了CRDU操作以及用户界面的调整。分散在表现层和数据访问层中的逻辑有可能只是处于性能方面的考虑才进行的重复。这些重复由一些系统总体功能的灰色地带导致,有时候我们都不知道该把他们放在何处,下面介绍几个常见的灰色地带。
1、数据格式化。若在客户端进行可视化,那么表现层必须了解很多上下文以及系统的逻辑。若将格式化放在业务逻辑层,那么业务逻辑层就必须提供合适的方法,供客户端调用并获取可以直接显示的字符串。我们会有几个选择,第一是存放两个数据,原始数据和格式化之后的数据。第二是存放原始数据,需要时格式化。第三是按照输入时的格式直接存放。我们当然会选择第二种。
2、CRUD操作。数据访问层应该仅仅用来处理数据库相关的操作,而业务逻辑判断又必须调用数据。
3、存储过程。原因类同与CRUD访问。在涉及到数据库流量的问题时,基本可以通过添加硬件或更换更好的硬件来解决,此外,在业务逻辑层中可以进行适当的缓存。
业务层的模式区分
在业务层的模式上有几种选择。首先是过程式模式,在面向对象开发兴起之前,业务逻辑只不过是一系列过程的集合,每个集合都用来处理来自于表现层的一个请求。因此好的设计都在于更好的去组织这些过程,减少代码和流程的冗余。基于对象的模式抽象程度越高,与数据模型的差距也会越大,因此若想创建一个领域驱动的对象模型,一般应该从领域着眼,而不是从数据库结构开始。领域驱动的设计一定会使数据库模型和领域模型之间存在不小差异。这个模式通常叫做领域模型。
基于对象的模式在开始时代价较高,但随着复杂性增长,其代价基本上也线性增加。换句话说,一旦你对领域模型有足够的了解,无论系统的复杂性如何,你都可以使用领域模型模式。在复杂性度量上,复杂性分为两方面,第一种是天生的,不可避免,无法协调需求本身,这类复杂性可以通过一些方法或多或少降低,不过不可能完全抹除,必须直接面对。第二种是来自于引入的复杂,这个来自于不同项目干系人意见的不协调,负面效果就是将团队引向错误的方向,错误的方向则会让抽象设计包含不必要或非功能性的功能,将软件逼上绝路。
需求的数量可以用来粗略判断系统的复杂性,认识到复杂性比解释如何找到复杂性更加简单。
事务脚本模式
事务脚本模式可能是业务逻辑层中最简单的模式了,它是一个纯粹的过程式模式。事务脚本模式鼓励你放弃所有的面向对象设计,将业务直接映射到需要的用户操作上,该模式的关注点在于用户通过表现层所能执行的操作,并为每个操作编写一个专门的方法。事务脚本模式易于理解和解释,每个必须的用户操作都在一个物理事务中开始。直至结束,不过数据访问通常被封装在另一些组件中,并不属于脚本。事务脚本适合应用于那些业务逻辑非常简单,且最好不大可能改变的简单场景中。在这个简易度和复杂性的概念上权衡非常艰难,更为重要的是,对简单和复杂的认知还依赖与一个人的态度和能力。你懂的!
简单是事务脚本模式最值得一提的优势,但是事务脚本有造成代码重复的潜质。所有实现了事务脚本的类型都可以看做是业务组件。每个业务组件都可以有一个或多个事务。比较流行的设计方式是将各个事务脚本分组,然后让每一组成为一个业务组件,这样每一组中的事务脚本在逻辑上都是相关的。另一种做法是将每个事务脚本用单独的类封装起来,这样每个业务组件都仅包含一个方法,换句话说,这里的业务组件就变成了一堆命令对象。若想将事务脚本的业务组件设计成命令对象,可以释放出一个接口,并将所有需要的参数以类型公有属性的形式暴露出来。
表模块模式
与事务脚本相比,表模块更有结构,表模块作为一个容器,将数据和行为组合在一起。业务逻辑被拆分为粗粒度的、表示整个数据表的组件。表模块的粒度不会下降到数据行的程度,即表模块类无法区分每一行数据。表模块模式应用中,使用基于记录集的数据结构是最常见的方式。例如Dataset、Datatable等。
对那些不需要太多抽象,且数据模型和对象模型之间没有太大差异的场景,表模块是个非常不错的折衷方案。与事务脚本相比,表模块基于一个概念模型。而不是一大批方法的松散集合。若系统中的表现层和数据访问层都是基于表状数据结构,那么表模块将是非常好的选择,这时,业务层即可为表现层提供直接可用的数据,有时甚至可以直接通过数据绑定实现。
表模块并没有比事务脚本复杂很多,不过若完全需要手工构造,那么花费的时间可能比简单的事务脚本多出很多。模式在提供了更多指导的同时,也意味着我们需要考虑更多规则,也就是编写更多代码。
内嵌的Dataset设计器提供Fill和GetData方法来基于整个数据表进行查询。从概念角度讲,表模块的另一个优势在于,无论底层数据源是什么,对于同一些功能都会得到同样的表模块类。表模块基于对象,但完全由数据库驱动。表模块并不适合表述复杂的众多实体。特别是对象模型和数据模型之间有显著差距的时候。表模块的优势是VS提供了强大的支持,实现起来简单,不过考虑到这些生成的代码类似于一个黑箱,优势也就成了劣势。当然,表模块这个结构模型不一定需要和VS紧密耦合。
活动记录模式
事务脚本和表模块都是过程式模式。但是表模块基于对象,不是一个对于基于对象业务逻辑建模模式。最终走向面向对象设计关键的一步是你认识到系统中的目标对象,业务逻辑和领域逻辑才是系统的核心,它是由实体之间必须的交互组成的。
活动记录是一种应用于相对简单的领域模型,但仍基于对象模式。另一种更加容易理解的说法是,活动记录基于数据表中的行,而不是表模块那样基于数据表。也就是说,活动记录对数据有行级别的粒度,而表模块关注的是整张表。活动记录模式归为数据源设计模式,广州达内而不是业务逻辑模式。活动记录模式就是指一个封装了数据表或视图的一行的对象,对象中可以同时包含数据和行为,活动记录对象的结构应该尽可能地接近于相关联的数据表结构。
在那些没有服务层的应用程序中,活动记录模式可以和事务脚本结合使用。
活动记录模式的成功依赖于两个因素:简单和框架。这两个因素紧密相关。我们最常见和熟悉的是LINQ- to - SQL。相对于简单的逻辑来说,活动记录非常合适。活动记录的另一个问题是对象和数据库表设计之间的绑定。若你不得不修改数据库,那么同时也要修改活动记录对象模型以及所有相关代码,从这个角度看,活动记录站在了领域驱动的对立面。
领域驱动模式
在设计领域逻辑时,若选用了过程式方法或活动记录模式,那么实际上采取的是以数据为核心的做法。驱动业务模型设计的并不是业务本身,而是业务中使用到的数据。
从长远看,以数据为中心的方法并不能很好的适应规模的增加。因为当系统的复杂性达到某个极限之后,哪怕是再添加一些新的需求,都会成为难处理的问题。领域模型关注于系统的期待行为以及系统正常工作所需要的数据。领域驱动设计力求将复杂性控制在一个可控(虽然仍旧很高)范围内,选用领域驱动设计并不一定需要使用领域模型模式,不过领域模型模式是最自然的选择。
领域模型描述了系统中涉及的实体,捕获了实体之间的关系以及数据在其中交换的过程。领域模型此刻还并不是一个软件,也不涉及任何软件或实现上的概念或原则。领域模型只是一个正式的模型,用来让技术团队和业务团队彼此理解并能良好交流。领域模型可以看作是活动记录的“大哥”,领域模型完全和数据库独立,是一个尽可能贴近真实流程的模型。领域模型模式使系统建模非常自由灵活,无需考虑平台和数据库的约束。此外,领域驱动设计是一种看待设计的方式,因此技能和态度起到了重要的作用。
在领域模型中,概念是用来建模的,这种做法充分利用了面向对象设计的优势,你可以使用到面向对象的所有特性,包括封装和继承,而同时又无需受到数据库结构的限制,这就意味着实体并不会察觉到丝毫有关底层使用的持久化机制。领域模型是由需求驱动的,且仅需要你理解问题领域,并用类对于逻辑建模。实现领域模型主要的障碍是对象和关系型数据库之间的不匹配,领域模型是一个面向对象的设计,和数据库或其他应用程序的约束完全无关,而今受限于其建模的业务流程,这意味着同样的领域模型可以重用于其他需要同样逻辑的任意场景。模型是和业务相关的,且仅和业务相关。
若没有任何O/R映射工具,例如NHibernate,那么很难实现领域模型模式。
小结
业务逻辑是系统的核心,不过并不是整个系统。业务逻辑设计上的选择将会影响到其他层,特别是持久化和数据访问层,这两个层加起来,就会项目的成败产生决定的影响。