后知后觉:企业应用架构模式(一)
 

2010-01-06 作者:twdwyp 来源:博客园

 

1 企业应用模式概述

1.1 企业应用的模式

企业应用领域要解决的问题在某些方面要比做一个工具软件、或者一个电信通信软件等复杂的得多,比如纷杂的企业数据,各具特色的业务规则,变化莫测的用户需求。因此企业应用开发技术从CORBA、COM、J2EE、_NET等等,层出不穷,每一种技术的出现,都为企业问题的解决题供了一种思路,一个选择。

既然企业的问题是特定,那么我们就可以把问题进行分类,并把每一类问题的解决方法记录下来,这样,就形成了一套我们解决问题的思路,这就是模式。模式的核心就是特定的解决方案,它有效且有足够的通用性。借用一下Christopher Alexander给出的模式的定义:“每一个模式描述了一个在我们周围不断重复发生的问题以及该问题解决方案的核心。这样,你就能一次又一次地使用该方案而不必做重复劳动”。

模式的关键点是它们来源于实践,他必须观察人们的工作过程,发现其中好的设计,并找出这些解决方案的核心,一旦发现了某个模式,那将是非常有价值的。当然,如果要学习一个模式,只是需要了解这些模式是干什么的、解决了那些问题、是如何解决问题的,就足够了。

企业架构的领域问题有许多,如分层架构、Web表现、业务逻辑、数据库映射、并发、会话、分布策略、异步通信、安全、错误处理、集群、应用集成、架构重构。在此只选择了其中企业级应用程序的分层、构建基于Web的用户界面、构建领域业务逻辑、将内存模块关联到关系数据库、事务与并发、无状态环境下的会话状态、分布策略等七个方面的问题进行了总结讨论。

1.2 企业应用的特点与术语

企业应用是什么是一个比较难以回答问题,但通常企业应用一般会涉及到持久化数据、会涉及到大量的数据、会涉及多人同时访问数据、会涉及到大量操作数据的用户界面屏幕,还会有一些特定的业务逻辑,尽管在企业应用中,业务逻辑通常是最没有逻辑的。

企业应用中性能的要求:

响应时间是系统完成一次外部请求所需的时间。

响应性是系统响应请求的速度有多快。

等待时间是获得系统任何形式响应的最小时间,如果在本机一般都会得到响应,如果在远程计算机上,响应时间就跟传输时间相关了。

吞吐率是给定时间内能够处理多大的请求量,对企业应用,吞吐率通常是每秒的事务数(tps)来度量。

在此处,对用户体验最明显的是响应性,有时候,为了响应性,损失一些响应时间或者吞吐率是值得的。

负载是关于系统当前负荷的表述,负荷可以是多种多样的,比如可以用连接的用户数来表示负荷,通常作为其他指标的背景:如20个用户响应时间5秒。

负载敏感度是指响应时间随负载变化的程度。

效率是性能除以资源。

系统的容量是指最大有效负载或吞吐率的指标。

可伸缩性是指系统中增加资源(通常是硬件)对系统性能的影响。一个可伸缩的系统允许在添加了硬件后,能够有性能上的合理提高。垂直可伸缩通常指增加服务器性能,水平可伸缩通常指增加服务器数目。

2 企业应用的分层

在分解复杂的软件系统时,分层是使用得最多的技术之一。计算机领域中到处都有分层的影子。

合理的分层可以1屏蔽底层的细节,2替换某层的实现,实现良好的低耦合。但是分层也有不足之处,1过多的层次会影响性能,2严格的分层在企业应用中会造成修改的级联,比如界面增加一个字段,会影响下面的所有层次。

当人们讨论分层的时候,常常不易区分layer和tier的区别,在人们的理解中,tier通常意味着物理上的分离,比如客户/服务器通常被称为”two-tier system” 。

企业应用的三个基本分层讨论:表现层、领域层、数据源层。

表现层:表现层处理用户与软件间的交互。表现层的主要职责是向用户现实信息并把从用户那里获取的信息解释成领域层或者数据源层上的各种动作。

数据源逻辑主要关注与其他系统的交互,如事务监控、消息系统,最主要的数据源逻辑是数据库,其职责是持久化数据。

领域逻辑也称业务逻辑,主要职责是根据输入进行计算、对表现层的输入进行验证、以及根据表现层的命令决定调度那些数据源逻辑。

在实际的应用中,严格的清晰分层是比较难以实现的,在有些应用中,表现层直接访问数据层,也运转良好;但是对于复杂的系统,为了日后的维护,我们需要保持这种三层的风格,并且在开发过程中至少也应该坚持某种形式上的分离,至少在子程序(函数)级别。

伴随着分离,有一种普遍原则:那就是领域层和数据层绝对不能依赖表现层,因为表现层是对用户的接口,是变化的最频繁的地方,解除依赖可以方便日后替换表现层。确定一个逻辑(方法)是否放到领域层或表现层有一种简单的方法,那就是,再增加一个表现层,如果这个方法需要重复编写,那么这个逻辑就应该是领域层的逻辑。比如,如果判断经理和职员来显示某个功能的逻辑,对系统来说就应该传入用户的id,传出true或者false。界面层只依赖于true和false。

3 领域逻辑模式(领域逻辑设计相关)

领域逻辑通常有三种方法:事务脚本、表模块、领域模型。在可以表模块或者领域模型的系统中,通常会再细分一个服务层,对于事务脚本能够实现系统,通常没有必要划分服务层了。如果在开发环境(visal studio,.Net)拥有大量记录集工具的环境下,使用表模块还是很吸引人的。

3.1 事务脚本

基于的思想:大多数企业的应用都可以被看作是一系列的事务,一个事务可能将某种信息看作是以特定方式组织的,然后另一事务会改变他,

事务逻辑脚本将所有的事务逻辑组织成单个过程,在过程中直接调用数据库,或者用一个简单的数据封装器。每个事务都由自己的事务脚本,尽管事务间的公共子任务可以被分解为多个子程序。

特点:许多事务脚本的设计中都有直接操作数据库的操作,有些把SQL语句直接放到数据库中。

*评论:

此处作者的事务脚本的概念在是,在程序编码当中,在一个或者几个方法中进行的直接操作数据库获取数据,进行判断逻辑、并回写数据库进行持久化的操作。这些操作为了完成某些业务,没有特殊的组织,一般没有经过精心设计的程序都可以归为事务脚本阶段。这是从编码风格角度来说的。

在此,可以基于事务的思想,可以提出其它优化方法:

1〉 使用数据库的存储过程来实现某些事务过程。书写方便,执行效率高,而且易于修改(不怕规则变动了)。目前我们的MIS系统中通常使用这种方式,但是,如果规则多了,事务脚本太多,管理不方便,而且,许多小的操作,不是频繁操作数据库的事务规则用数据库脚本就不合适了。

2〉 基于《GOF设计模式》中 Command模式,将事务脚本组织起来,方便调用和管理。

3〉 基于《GOF设计模式》中interpreter模式,将自己的脚本抽取为领域规则,使用自己的规则解释器去解释这个规则,最终形成自己的“领域标签”,终极模式是形成类SQL的规则语言。目前有些成熟的产品已经形成了自己的领域规则,并有自己的规则编写器,这样程序的二次开发问题能解决好多。软件产品中使用这个方法可以降低维护成本及解决产品定制问题,但是在项目中就不太方便了。

3.2 领域模型

基于的思想:如果领域逻辑规则太复杂了,就需要使用面向对象的方法来解决这个问题。面向对象将自然界的物体都表示为属性(数据)和操作,因此足以表达负责的逻辑。但是,映射到数据库比较困难,通常使用活动记录或者O/RM产品。

EJB和POJO(plain of java object 普通java对象),在java中,如果领域逻辑比较复杂涉及到继承等操作,建议使用POJO,只有表结构与对象比较类似的情况下,使用ejb.

基于领域模型或面向对象的参考书有:[Larman],[Flower AP],[Hay],[Martin and Odell]。

3.3 表模块

表模块是用一个类对应数据库中一个表来组织领域逻辑,而且使用单一的类来包含对数据进行的各种操作。它与领域逻辑的主要区别在于,如果有许多订单,领域模型使用多个类来处理订单而表模块使用单一的类来处理所有订单。

表模块的长处是允许类和数据绑定在一起,可以充分利用关系数据库的优点。表模块看起来和常规的对象很象,但关键区别在于它没有标识出来所代表的常规对象。

表模块可以是一个实例也可以是一个静态方法的集合。

表模块的入口:表模块封装的是一个记录集,那么形成记录集的入口通常有两个,一个是在构造函数或者初始化入口中使用查询语句,另一种就是在入口中传递一个表数据(如recordset),这样的优点是封装了数据的生成,这个表模块可以面对多个数据源,并且你可以不用数据库就测试领域逻辑。缺点是多增加了一个用来生成数据的类。

比较重要的是,此处的表模块并不是必须针对每一个数据库物理表创建一个表模块,其更多的是针对一个虚拟表结构(如物理视图或者一个查询语句)创建模块。创建表模块。

如果使用表的结构与对象结构比较类似,则在领域模型中可以使用活动记录来解决,如果程序中有一个公用的表的结构,实际开发中,人们多倾向于使用表模块的方式来解决问题,比如微软平台中有一个ADO很好的封装了表,因此在微软的平台中,多使用表模块,而在Java中则没有这点条件,所以java中使用领域模型的比较多。

在微软.net平台中,尽管使用无类型的表模块更能够实现平台无关性,但是有许多实例可以证明使用.net强类型数据集更好。

3.4 服务层

在领域模型中,通常可以再划分出来一个服务层,服务层在领域模型之上,封装了领域模型的实现。服务层定义了系统应用的边界和从客户角度看到的各种操作的集合。

业务逻辑的分类:服务层是一种组织业务逻辑的模式,通常将业务逻辑分为两类,应用逻辑和领域逻辑,领域逻辑解决领域问题,应用逻辑与应用的职责相关,通常解决工作流、应用集成的问题。如果应用逻辑与领域逻辑分离,则能是领域逻辑更好的复用。

实现方法:服务层可以只是定位到系统操作的集合,这样只是提供接口对外调用,没有复杂的逻辑即领域外观法,领域逻辑也可以提供操作脚本的方法,将数个操作脚本组成一个类,命名为**服务。

4 数据源架构模式(数据源入口设计相关)

4.1 表数据入口

作者表述的表数据入口比较简单,就是使用一个类,封装了对数据库表(视图)的操作,主要有查找,添加,删除操作,这些操作是根据参数直接使用SQL语句查询数据库的。因此多数.net平台下的程序写的方法都可以认为是表数据入口。使用ado 中dataset或datatable对象的来保持数据的也算是表数据入库,只是有了保持数据的操作。

4.2 行数据入口

最初看到行数据入口,还以为是封装ado中datarow的操作的,看到示例代码才明白,就是以类封装表(视图)中一行记录的方式,类本身用属性或者get,set方法得到字段的值,再加上增、删、改、查的方法。这种封装方式,我在重构别人的系统的时候吃过苦头。系统是VB6的,数据库某个表字段一修改,就要改类的增、删、改方法中的传递的值,结果类的接口就改变了,VB6把类都作为com对象的,接口改变是要重新生成CLSID的,工程中所有引用此类的方法都要重新编译(编译一边不出错,再加上打包部署,一个上午就没有了,仅仅为了一个字段)。最后,我把大部分的类的方法传递的参数都修改为 recordset 或者 datarow了。不过,每次想到那个程序,就会提醒我,不是用了类的系统就是面向对象的系统了,拙劣的模仿害死人。

4.3 活动记录

用书中的表述是活动记录和行数据差不多,就是在行路口封装的类中有了一些业务逻辑判断的方法,比如a>b之类的。这个可以作为区分的方法吧。

*我认为,活动记录和行数据就是一个东西,只是在不同的应用场景中的名字,活动记录是在纯粹面向对象解决领域逻辑的术语空间中的名字,行数据的设计方法有有些结构化设计的影子。(术语空间就是说我在家庭这个术语空间内称为老公,在公司这个术语空间内称为职员,虽然老公、职员都是我一个人,但是如果换一下,就天下大乱了)

4.4 数据影射

数据影射是很管用的,可以在对象和数据库彼此独立的情况下在两者之间可以移动的一个影射层。

数据影射的方法比较多了,比较笨的方法,我可以用一堆影射类来对应领域层的类,写sql脚本的方法入库,我稍微聪明点的话,就是写一个通用类,加上判断数据库中改字段的元数据,自动生成sql语句插入到数据库中,我很聪明的话,就会写一个可以通过读取配置进行数据影射的类了,呵呵,当然非常聪明的话,就去找一个nhibernate,或者其它ORM产品了。不过,用ORM一定很好么?我目前的项目用了一些,只是在单表的情况下用,如果涉及到动辄4-5张表关联的情况,如果加上代码表可能有10张表的情况,可能就不太灵光了。

5 对象关系行为模式(数据源入口设计相关)

OR影射,实践中最难的就是架构及行为方面。行为问题就是如何让各种对象从数据库中读出来又存到数据库中。

5.1 工作单元

如果将一个数据库中数据读取出来到一个对象中,修改过某些值后,则调用这个对象的更新方法就可以更新到数据库中,这是没有问题的。如果程序中在两个地方都调用了同一条记录,那么对数据库更新谁先谁后就有问题了。所以需要使用工作单元的机制,当你读取的时候,需要注册一下,保证你只取出来一份,当你更新的时候,工作单元负责你们更新的问题。避免数据覆盖。如果使用过Ado的话,可以发现微软的ADO(ado.net)中已经实现了这个问题。

5.2 标识映射

标识映射,保证你每次取数据库数据,如果已经生成了此数据的对象,不用每次访问数据库。直接将此对象返回。这个挺像《GOF设计模式》中的singleton模式,一个镜头变量或者cache可以解决的,标识映射通常放到工作单元中实现。

5.3 延迟加载

在这个语境下解释:就是当对象实例化的时候,并不加载从数据库中加载这个对象,而是加载这个对象的代理,到真正使用这个对象的时候,再加载数据。这样效率更高一些。《GOF设计模式》中的agent模式是延迟加载的一种解决方法。在动态语言中、和集合类中使使用虚代理比较好,但是在静态语言中使用虚代理比较困难。

值保持器:

重影:是允许对象加载部分状态数据,在访问某个域的时候,再完全加载这个对象。重影本身也可以看作是虚代理,对象的本身就是自己的虚代理。

6 对象关系结构模式(数据源入口设计相关)

介绍在内存中进行映射数据库结构的结构特征,必须需要那些属性和标识来表达物理结构。

  • 标识域

标识域主要是介绍了数据库中的主键的问题,在更新过程中,肯定需要数据库主键。如何来维持和构造单键及组合键,并成功构造出SQL语句。键的问题是涉及到系统能不能通用的大问题,因此值得好好研究一下。

键可以分为单键及组合键,生成单键的方法目前有:数据库的自增列,数据库技术器,GUID,自己生成(Max函数,或者使用键表);通常使用键类来实现组合键。

  • 外键映射

对象之间常常是有关联的,对象的一对多的关联关系持久化到关系数据库中,通常使用外键关系来存储。当然,如果是对对多的关系就得使用关系表映射了,如果有一个没有后项指针的(?)的集合域, 就应该考虑多的那一边使用依赖映射,这样可以简化集合映射。如果对象是一个值对象,则使用嵌入值。

外键映射在对象间通常有三种用法:

单值引用,就是一个对象与另一个对象一一对应。

多表查询: 将多个表关联到一块,一次查询出来。

引用集合:在一个对象中维护另一个对象的集合。

  • 关联表映射

对象之间的关系是多对多的关系,对应数据库中将使用关系表。关系表中数据的读取通常使用 join的SQL语句完成。

  • 依赖映射

如果一个类,比如独生子女家庭中的家庭对象和孩子对象。家庭包含孩子对象,通常加载一个就可以将另一个加载出来,而且其中一个对象中有另一个的引用,比如家庭对象有此孩子对象的引用。此处与单值映射通常近似。

  • 嵌入值

如果将数值或者属性封装为对象,则此对象为依赖对象,此对象没有自己的持久化方法当父对象加载的时候,将同时加载。

  • 序列化LOB

将对个对象序列化为一个xml或者二进制格式进行持久化。这种方法比较少见。

  • 单表集成

将一张表中的每个属性列映射为同父类的不同子类。

优点是数据存取简单而且类层次的调整不会影响数据库结构,缺点是数据库单表可能太大,而且空列太多。SQLSERVER中不建议为NULL。据说Orcal对整理闲置空间很有效。可以看看相关资料了。

  • 类表集成

数据库中对应类层次的建不同的表。有一张表存储公用属性。

  • 具体表映射

数据库中对应类层次的建不同的表。每张表存储自己的属性。

  • 继承映射器

使用数据库映射器来模拟类的集成关系,避免在关系数据库中来关联。

火龙果软件/UML软件工程组织致力于提高您的软件工程实践能力,我们不断地吸取业界的宝贵经验,向您提供经过数百家企业验证的有效的工程技术实践经验,同时关注最新的理论进展,帮助您“领跑您所在行业的软件世界”。

资源网站: UML软件工程组织