J2EE应用系统的MDPB开发方法
 

2010-01-18 作者:林镇锋 来源:CSAI.cn 

 

摘要:本文介绍了一种适用于面向对象的J2EE应用系统的开发方法,其核心思想是MDPB-Model Driven Pattern Based即基于蓝图的模型驱动设计。

概述

一般的软件的分析设计过程为:需求调研,需求分析,概要(架构)设计,数据库设计,详细设计,而面向对象的分析设计方法有很多种,其中RUP堪称集大成者,但是RUP的分析设计过程过于复杂。现实中很少有项目能够完整的遵循UML建模过程执行的,从设计用例图、强固图、时序图、类图,一直到部署图一步步的进行推导。绝大部分项目只使用了常用的UML图,例如类图,时序图,用例图,而在后续的设计开发过程中,几乎完全将之搁在一边,这些图仅起参考启发的作用。ICONIX方法对RUP进行了提炼和简化,但是仍然显得步骤繁琐,缺乏切实可行的步骤。

在面向对象WEB应用系统的软件分析和设计过程如何具体操作,至今没有一种很好的实践方法。有鉴于此,笔者通过几个项目的实践,总结出一种更加简便的分析设计过程,能够很好的适用于面向对象的J2EE WEB应用系统的开发,也同样适用于富客户端应用系统的开发。

这个过程依据通用的需求调研、需求分析、架构设计、详细设计到编码实现的5个步骤,描述了针对一个J2EE WEB应用系统,每个步骤的工作如何具体开展,重点在于如何依据架构模式从领域模型推导出业务逻辑层的代码,其思想的核心是MDPB-Model Driven Pattern Based基于架构模式的模型驱动设计。

需求调研

需求可以划分为2个层次:业务级需求和软件实现级需求。

需求调研主要目的是获得用户的业务需求,可以使用业务流程图来表述。业务流程图从用户的角度描述了真实完整的业务过程business process,其中部分流程可以用软件实现。业务流程图是业务级的需求。当然,客户的期望和业务需求仍然需要用文字进行描述。

绘制业务流程图

由于业务流程图与实际业务过程是一致的,并且使用用户习惯的专业词汇,因此需求分析人员和最终用户可以一起讨论制定,在经过客户方审核认可后定稿。业务流程图可以从整体业务流程图出发,进行业务划分,层层分解得到每个子业务的流程图,因此业务流程图分为多个层次。

在需求调研阶段不可忽视客户的期望,在英文词中,需求是REQUIREMENT,而这些期望是NEED,往往也被称为需求背后的需求,我们要将这些期望记录下来。

需求分析

需求分析是根据需求调研的结果,包括业务流程图、规章制度、表格等进行业务领域分析,同时使用用例描述、界面原型、领域模型3种技术进行分析。

用例描述可以记录业务级需求,也可以记录软件实现级需求,通过用例分析可以推导出界面原型和领域模型。

界面原型是软件的外在表现,体现了用户与软件进行交互的过程。一个软件最终展现给用户的就是人机交互的界面,人机交互界面就是需求的一种实现,引入界面原型可以在一开始就给用户一种直观的感受,“啊,这个软件是这样子的,嗯,这是我想要的功能!”。

领域模型使用类图来表述了一个软件的静态结构,是从客观世界中抽象归纳出来的模型,是软件实现级的需求。

需求分析的结果是编制出需求规格说明书,将前述的业务流程图、表格、业务特性、功能需求描述、界面原型、用例描述汇总在一起。另外,需求分析过程中要注意从功能需求中提取出可以作为重用组件的需求。

需求分析涉及的关键方法有:领域模型设计、用例分析。

从业务级需求推导出系统实现级需求

用例描述

用例描述使用主成功场景和扩展场景来展现交互过程的。一个场景就如同一个剧本描述了各个演员(actor)的对白和活动,故事情节的发展。

软件实现级需求分为功能需求和非功能需求(包括性能需求,易用需求,安全需求等)。功能需求描述、非功能需求描述是从应用系统实现的角度描述系统的行为和特性。功能需求分析主要是依据业务模块划分出功能模块,并对每一功能模块进行表单、属性、操作的分析。进行功能需求分析时,业务连贯较强的功能模块可以使用用例分析技术来表述一个软件的内在动态联系。用例描述可以作为软件实现级的需求描述手段。

用例主要使用操作者、主成功场景、扩展场景、前置条件几个要素描述软件内在的动态联系,这种内在关系是界面原型上无法表述的,也避免了对功能模块分而治之导致功能之间缺乏连贯。在需求调研时,我们了解到各类用户的业务活动,但是业务活动之间的联系缺乏一种清晰完整描述方法,用例分析技术就是为了解决这个问题。

从用例出发,我们可以推导出用户的界面原型的概貌,人机之间交换信息的过程,以及整个业务流程中各用户是如何参与互相配合的。当然,界面设计还要依赖用户使用的各种表格。

从用例能够推导出领域模型。例如在用例的场景描述中:用户提交某个表单后,系统进行检查并给出一个是否成功的提示。这意味着某个实体上需要提交、检查的操作。

如何编写出用例,在《编写有效用例》一书中有非常详细的阐述,本文不作赘述。值得注意的是,用例场景的描述文字远比用例图重要。

绘制界面原型

根据调研结果,绘制界面原型,可以是HTML,或者visio等。界面原型最好能够有人机交互过程,使用户能够直观的体验到自己的需求如何被系统实现。通过界面原型可以探明业务需求的风险,在早期与用户就需求达成一致。界面原型的设计可以依据交互式设计的原理进行,这里不做详述。

领域模型

什么是软件的核心和价值所在?是软件解决业务领域问题的能力。作为需求分析人员应该关注业务领域。领域模型使需求分析人员将注意力集中在如何解决领域问题,而不涉及技术实现手段,另一方面,可以将需求分析人员的想法传递给开发人员进行详细设计和编码开发。

创建领域模型

创建领域模型的步骤如下:

1. 根据需求(现实世界)建立词汇表

2. 从需求和词汇表中发现名词和概念,作为业务领域的候选实体类

3. 在实体类之间添加必要的关联来明确彼此关系

4. 添加实现需求的必要属性

领域模型与数据库模型、信息模型的区别

一个经常引起争论的问题是,领域模型与数据库模型有什么区别?

两者有一些细微的差别,我们尽可能的使领域模型与数据库模型一一对应。当然领域模型使用了面向对象的方法,如继承、接口以及其它设计模式。在简单情况下,领域模型只保持了属性,而没有操作。相同的是两者都是由多个类或者实体组成的关联网络。

可以为两者的映射确定好规则,UML有一个缺点,太灵活,可以根据实际情况来选择不同的实现方式,但是领域模型毕竟是给开发人员,最好确定映射规则,以便理解。多种不同实现方式导致各自实现代码不同。

领域模型不涉及具体的实现,我们可以使用EJB实现,也可以使用POJO实现,当我们使用了设计模式时,往往很难使用EJB来表现,这时只有使用POJO。事实上,只要我们确定了领域模型与数据库模型的映射规则,就可以使用MDA工具输入领域模型来生成EJB或者POJO代码。

另外,通过详细设计,数据库模型还可以包括存储过程、触发器、索引等数据库特有的对象。

领域模型也不同于信息模型,信息模型描述的范围包括实体的属性、类之间的关系(继承、关联、聚合)、分包,领域模型在此基础上还描述了实体类和接口的行为。

词汇表

词汇表中包含了用户使用的专业词汇,在领域模型中起到提纲挈领的作用,同时规范了关键的业务词汇用语。通过浏览词汇表,大概可以了解领域模型所要解决的问题领域。在开发过程中,如果需要新增属性或者方法,开发人员可以依据词汇表来给新的属性、方法命名,能够有效的避免了最终程序中用词不一致的情况,提高代码的可读性。

领域模型的内容

领域模型设计使用UML的类图来表现。

领域模型可以包括实体和服务。

实体可以标识作为对象的基本定义。标识有两种:有意义的标识,例如:人的身份证号码。无意义的标识,例如:同一账号的两笔等额的存款交易都有一个计算机产生的交易号。

实体可以有行为,例如:新开账户、取款都是是账户实体的一个行为,这些行为是业务领域的实际操作。

另外,还有一种实体称为值对象,这种实体基本上没有业务方法,主要是起到类似数据字典般的查询作用。

当领域中的一个重要行为或转换操作不是实体对象本身的职责时,把操作作为一种独立的接口加入模型,并声明为服务。服务指为客户做什么,代表一种行为,而不是一个实体,是一个动词而不是一个名词。例如:将资金从一个账户转移到另一个账户的功能是一个领域服务,包含了明显的业务规则。转账的服务不能属于任何一个账户对象,因为操作包含两个账号和一些全局规则。服务只有行为,一般作为接口提供操作。

领域模型如何映射到设计和实现

传统的办法是分析阶段时创建分析模型,到了设计阶段根据分析模型的启发创建设计模型。

这种方法需要创建两个模型,而且分析模型和设计模型之间没有直接映射关系,是在人脑中进行加工转换的,这样无法保证设计模型完整的继承分析模型。如果模型无法映射到设计与实现,其价值何在?

由于架构固定,分析模型能够根据架构映射到设计和实现,因此设计模型可以不必到达真实类的粒度。将两者合一,使用一个分析模型(也称作领域模型)就可以贯穿分析设计过程。

以WEB应用系统开发举例,通过一个固定的J2EE架构可以将领域模型的实体的属性对应到EntityBean,方法转换为ApplicationService的方法,默认的基本方法(增删改查)不需要在领域模型中定义,这是为了让分析人员将注意力集中在核心业务上。服务一般转换为接口,被ApplicationService实现。从另外一个角度也可以认为通过MVC将领域模型的实体分解为Model-EntityBean和Controller-ApplicationService,服务直接转换为Controller-ApplicationService。

小结如下:

实体默认的增删改行为(领域模型不需要定义)对应到Application Service的行为

实体的行为(领域模型定义)对应到Application Service的行为

服务对应到Application Service的行为,或者作为接口被Application Service实现

举例:

账户实体有行为取款和存款。这两个行为被映射到ApplicationService的2个方法:

deposit(Account acc,int qty);withdraw(Account acc,int qty);账户的属性映射为Account的EntityBean。其余的SessionBean,DAO,Action等都按照架构的原则生成。

另一方面,由于业务经常发生变化,保持一个稳定、易于修改的业务领域层显得十分重要。通过固定的J2EE架构,一旦业务发生变化,可以快速定位到对应的业务实体和业务逻辑,提高软件的可维护性。

使用模式

分析模式可以在领域模型的前期引入,取得初始化模型,不是从零开始,而是在一个已有的基础上来建模,可以提高效率。设计模式可以在领域模型的后期引入,去优化调整模型,删繁就简,使提炼后的模型更加优美实用。

架构设计

架构是一个很宽泛的概念,为了更加清楚的说明问题,在软件设计阶段,我将架构简单分为3种:应用系统的功能架构、技术架构和物理部署架构。功能和技术架构设计指导了整个团队如何分工协作进行详细设计。应用软件的性能问题往往是由架构设计来决定的,因此需要在架构设计时考虑性能问题。

功能架构设计

应用系统的功能架构设计解决软件在具体实现时使用的架构,代码如何组织,各个模块之间如何通讯,各个层次之间如何通讯,非功能需求如何嵌入功能需求中去,以及重用组件与各个子系统之间的接口关系。在业务流程图中对业务进行了划分,一般来说,可以在业务划分的基础上进行功能的划分,此时需要根据软件实现的一些原则进行分包结构的调整,例如:稳定依赖原则、稳定抽象原则、无循环的依赖原则等。

功能架构设计示意图

技术架构设计

技术架构设计主要决定采用什么技术路线、通讯机制等,例如采用J2EE的架构。采用一致、稳定的技术架构,有助于提高技术人员的开发效率,整合已有的重用组件和其它系统的功能模块,因此可以通过架构蓝图来事先规定每个项目采用的技术架构。

架构模式

依据Sun的Core J2EE Patterns列举的模式创建适合的架构模式。

应用软件的架构设计应该将业务逻辑代码集中在ApplicationService中,封装和集成后端代码,保持业务层前端代码要薄,只是转发,业务层后端代码只是基本方法(增删改)。

ApplicationService集中体现了领域模型中类的操作和服务。SessionBean负责事务的控制,DAO负责数据查询。

用户界面层有2个职责,分别是表现层和页面跳转逻辑层。

在这个过程中,可以采用AOP将非功能需求从功能需求中解耦出来。

架构模式示意图 

部署架构设计

物理部署架构是指将软件的各个功能模块按照一定的架构分别部署在一个或者多个中间件软件上,这些中间件软件可能是分布在多台服务器上的。例如,一个复杂的部署架构可以将软件的不可间断的核心业务模块进行集群部署,提高软件的可靠性,另外的模块分布部署在性能不等的服务器上以满足不同负载的需求,而所有这些模块都是依赖于基础模块的,因此基础模块也可以单独部署。另外部署在内部网或者互联网也是需要考虑的重要因素,需要为此在软件里设计不同的安全措施。

在设计阶段考虑部署架构,是对软件的可靠性、安全性、性能,中间件平台和服务器配置要求的综合考虑,避免在系统测试阶段或者正式上线后才发现软件存在重大的设计缺陷。

架构设计阶段的交付物是概要设计说明书,其内容应该包含应用系统的功能架构设计、技术架构设计和物理部署架构设计三个方面的内容。

数据库设计

数据库设计过程是领域模型的一个间接成果,按照一定转换规则根据领域模型可以映射为数据库实体模型。

使用OO-R映射或者人工设计数据库实体模型都不是什么难事,表的定义几乎可以与领域模型中的实体一致,当然,除了数据库数据类型的转换外,还需要考虑到实现过程中的性能、易用等原则,适当增加冗余字段、索引等。

详细设计

面向对象详细设计应该怎么做,详细设计文档应该怎么写?我想是很多人经常感到困惑的事情。传统的详细设计文档分为输入、算法、输出,似乎和面向对象联系不到一块。

详细设计比领域模型更接近代码层,详细设计是以上所有工作成果的延续,它承接界面原型设计,在界面层上继续修订,完成界面布局设计、界面跳转逻辑设计,形成最终的界面设计;以领域模型为基础,在架构设计指导下,使用具体编程语言,形成POJO、EJB、dotNET的实际代码;数据库实体在这个过程被进一步精化,并会根据实际情况设计出索引、视图、存储过程、触发器等数据库对象。

综上所述,详细设计可以分为3个方面开展:业务逻辑设计、数据库设计精化、界面设计精化。以下只对业务逻辑设计作介绍。

在本文例子中,由于主要的业务逻辑代码集中在applicationService,因此详细设计就是确定applicationService的业务逻辑方法如何用伪代码实现,以及如何调用其他模块的applicationService类方法。

举例

某一个applicationService类的一个方法的详细设计如下:

XXposting()

if 领用了项目的物资 then

1. call XXAppSvc,生成一张从项目调拨到备品配件的调拨单,状态设置为“完成”,

2. call YYAppSvc,生成一张从备品配件调拨回项目的数量为0的预调拨单,状态设置为“未完成”,

3. call ZZAppSvc,生成一张申购单,状态为“新增”,

4. updateStatus,更新抢修领料单的状态为“已过帐”

Else if 选择待分配物资 then

updateStatus,更新抢修领料单的状态为“已过帐”(不必生成调拨单)

这种详细设计类似于伪代码,或者代码注释,篇幅很小,但保留了最有用的设计思想,可以使开发人员专注于写出最有用的代码和文档。 

编码实现

从领域模型驱动生成业务逻辑代码和实体对象代码,根据架构蓝图生成代码层次结构,实现填空式编程。具体描述:领域模型中的实体属性生成VO和Entity Bean,实体行为生成Application Service,实体的分包生成各个模块,根据J2EE Web应用的架构蓝图生成整个应用的代码架构,即各个层次的对象协作关系。代码由业务逻辑层和表现层2部分组成,在业务逻辑层为delegate/fa?ade/appsvc/vo/entity/dao,在表现层为action/js/jsp,另外,根据数据库设计的结果生成实际的数据库表和对象,作为代码编写运行的调试基础。

在业务逻辑的详细设计中已经设计出Application Service的各个方法的伪代码或者编码思路,因此在编码实现阶段开发人员将根据详细设计文档在生成的Application Service方法中编写代码。

从领域模型和架构蓝图到业务逻辑代码

同时,开发人员可以根据详细设计阶段更新后的界面原型来实现表现层代码,即展现页面-jsp和javascript,页面跳转逻辑-action。

从界面原型到表现层代码

结语

通过几个项目的实践,验证了这种开发方法具备很强的操作性,每一步都是为编码作准备,没有冗长多余的文档,既注重编码实现,又兼顾分析与设计的过程。敏捷方法过分注重编码,不提前进行设计,这要求每一个人都具备较高的技术水平,直接将设计融入到编码中,在现实中具备这种实施条件的团队很少,而RUP属于重量级过程,过分注重分析设计,项目组无法顶住长期作设计而不编码的压力。我认为,开发方法保持平衡与简洁很重要。首先要取得分析设计与编码的平衡,不能忽视分析设计的重要性,也不能先射击再瞄准,直接就开始编码。其次开发方法需要简洁,分析与设计过程的每个步骤耗费工作量不大,文档精炼,且能取得切实的效果,为编码提供具体的帮助。

参考文献

编写有效用例, Alistair cockburn著

模型驱动设计-软件核心复杂性应对之道, Eric evans著

统一软件开发过程, Ivar Jacobson,Grady Booch,James Rumbaugh著

分析模式-可复用的对象模型, Martin Fowler著


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