UML软件工程组织

 

 

从 UML 模型衍生出 Web 服务
2008-09-10 作者:Scott W. Ambler 来源:IBM
 


第 1 部分: 建立过程

按照下面几步简单的步骤,您可以把面向对象的应用程序组织成具有内聚功能的软件包,这些软件包可以通过 Web 服务访问。在本系列的第一部分,Scott W.Ambler 勾勒了一幅进行快速而且简便的转换的指示图。
Web 服务:无疑您已经看到好像有无数的杂志文章和书籍告诉你它们是多么的丰富多彩以及它们是如何革新构建软件系统的方式。不幸的是,这些发表的文章很少着眼于现存的设计和标识出应用程序潜在的可以提供的 Web 服务。本系列正是教你如何解决此问题。

根据 Designing Object-Oriented Software一书中提到的,并在 Building Object Applications That Work一书中(请参阅 参考资料中这些书的链接和其它推荐的读物)得到扩展的技术,本系列文章介绍了一个过程,这个过程是用于分析传统的对象设计,目的是为了把它部署到分布式环境。我已经在过去的基于 CORBA 和 J2EE 的系统里使用了此方法,在 Java 编程语言中现在正在使用它来标识和构建 Web 服务系统。据我的经验,即使底层的技术随着时间的流逝而改变,但是设计过程依然不变。

为了阐明这个过程,在这一系列文章里,我将谈到一个假想的案例研究,这个案例是一个经过简化了的称作 SWA Online 的电子商务系统。这个应用系统是一个在线的店面系统,顾客可以在此下订单,并把商品运回家。 图 1 展示了一个高级分析类图,它使用统一建模语言(UML)符号显示主类及其相互关系。它描述前端(很可能作为 Java 服务器页(Java Server pages,JSP)的一个集合被实现)为一个带有 <<application>> 模板的软件包。 维护库存和运输的类似的应用程序也存在但是没有出现在这个例子中。其他的类是一些组成系统的主交易类;其中的一些详细资料在这一系列的以后文章中介绍。

图 1.SWA Online 的初始类模型

SWA Online 项目小组决定采用 Web 服务方法,因为系统要求一个既可伸缩也非常健壮的解决方案。这个解决方案要求可伸缩,因为项目小组将能够根据需要将服务部署到各个单独的环境;这个解决方案要求健壮,因为小组想可以选择扩展其服务到外部组织或者将来在某些地方用 Web 服务提供者(WSP)提供的服务替换系统。在首版里,小组打算在防火墙的后面实现大部分的 Web 服务,这是许多组织使用的通用策略。

在面向对象设计中标识 Web 服务的过程

为了从面向对象应用程序派生一套 Web 服务,需要标识 域包(domain package)― 商业类/域类的高级集群 ― 和每一个软件包提供的服务。这些软件包于是可以用所选的技术实现(对我们的例子来说,我们使用 Java 平台)并在环境里部署。实际上,您是正在把类分布到这些软件包里然后通过 Web 服务把它们集成到一块。所以,要问的一个重要问题是,“每一个软件包应该提供给其客户端什么服务?”为了回答这个问题,本系列将采取下面的步骤(通常采用迭代的方式完成)从面向对象模型中标识 Web 服务。

  1. 简化持久性和系统类
  2. 简化继承和聚集层次结构
  3. 标识类契约
  4. 标识域包
  5. 标识域包契约
  6. 定义 Web 服务接口

在这一系列接下来的三部分,我将详细深入探究这些问题。您将学会如何简化 UML 模型、标识域包和定义 Web 服务。在本系列最后,您应该能够有效地将现有的模型和面向对象应用程序转换为您的商业伙伴服务可用的 Web 服务。

第 2 部分: 通过简化去粗取精

标识 Web 服务时首要的任务就是从概念上简化对象设计。那样,当您向前推进此过程的时候,您仅仅需要集中精力在主要的方面。

在这一系列的 第 1 部分,我回顾了采用 Web 服务的方法为一家在线商店店面重构现有的面向对象设计的策略。这一部分探讨了过程的头两步:简化持久性和系统类以及简化层次结构。这两步集中在概念上简化您正在处理的问题。

一个通用的体系结构策略就是把应用程序分层为不同的类类型。我通常采用一个五层方法构建应用程序:

  • 用户界面(UI)类封装组成系统用户界面的元素,例如,HTML 页、GUI 屏幕和打印/电子报表。
  • 业务/域类,也称为 实体类,实现应用程序里的基本域类型,例如, 图 1 中的 Order 和 Customer 类。
  • 流程类实现与几个类有关的复杂的业务逻辑,例如那些由潜在的 Web 服务提供的。
  • 持久性类封装对持久性存储的访问,包括关系数据库、平面文件和对象库。
  • 系统类封装技术功能,例如进程间通信或者记录错误日志的方法。

这个方法在我的一本书, The Object Primer 2nd Edition: The Application Developer's Guide to Object Orientation里面有更详细的介绍(请参阅 参考资料)。

图 1和这一系列的所有其它示例都是从 SWA Online 示例中抽取出来的,请参阅 第 1 部分了解细节。

图 1. SWA Online 的初始类模型

步骤 1:简化持久性和系统类

为此目的,标识面向业务的 Web 服务,您现在将需要选择忽视持久性和系统层。持久性层可以用几种方式实现,包括硬编码的 SQL 、数据访问类(如 Java 数据对象(Java Data Object,JDOs))、一个服务(如 EJB 的容器管理持久性(container-managed persistence,CMP))或者一个或多个 Web 服务,但并不限于此。相似地,系统层可以作为一个 API、包装器类集合或者 Web 服务实现。这些对象存在并且重要,但是由于简单化的原因,可以把它们的存在看作理所当然的,所以您可以集中精力在事情的业务方面。 图 1不包括任何持久性或者系统类,但是如果包括了,您将创建一个我们的模型视图,它没有显示这些类。

步骤 2:简化层次结构

为了标识服务者,继承和聚集层次结构常常可以被简化。对于继承层次结构,有一条经验可以遵循,如果一个子类没有添加新的契约,那么它实际上可以被忽略,并且通常您可以把一个类层次结构看作单个类。在 图 2 概述的 Address 类中, Territory 类层次结构可以通过忽略 State 而被简化,因为它没有添加新的契约。而且,现在我会倾向于把整个层次结构简单地看作一个实体,因为剩余的类 Country 仅仅添加了一个契约,坦白地说它并不是那样令人兴奋。

图 2. Address 类

对于聚集层次结构,可以忽略与聚集层次结构外部的其它类不相关联的任何“局部类”。在 图 1 里,您可以看到 OrderContact 可以被忽略,因为 Order 是唯一与之关联的类。通过分解聚集和继承层次结构,您简化了模型,在定义子系统时使其更易分析。

简化努力的结果是 图 3,正如您所看到的,它是一个可以使用的比 图 1中的原先模型更小的模型。

图 3. SWA Online 的简化的类模型

一旦您的模型被简化,下一步就是分析设计并开始把它重组为包。这是这一系列的下一部分的主题。到那时,您应该考虑如何把这里概述的技术应用到自己的面向对象的应用程序中。

第3部分:标识域包

在为应用程序标识可能的 Web 服务之前,您必须首先标识希望通过那些服务访问的功能内聚包。在这一系列的第 3 部分,Scott W. Ambler 向您展示如何组织您的应用程序使其成为一个更好的 Web 服务。

在这最后一部分,我将从概念上简化 SWA Online 的对象设计,SWA Online 是一家在线商店店面,它能让客户下定单并把商品运送到客户家。接下来的两步就是标识由余下的业务类提供的类契约,然后标识可能的域包 — 最后实现任何 Web 服务的相关类集合。

步骤 3:标识类契约

一个契约是其它对象可以请求的一个对象的任何服务或者行为。换句话说,它是一个直接响应来自其它类的消息的操作。契约定义了一个类的外部接口(也称公共接口)。例如,图 1 显示 Item 类的契约包括一些操作,如 reorder()、getPrice() 和 getFullDescription()。(图 1 和这一系列的所有其它示例都是从 SWA Online 示例中抽取出来的,请参阅参考资料获得到这一系列的第一部分的链接。)为简单起见,您可以忽略图 1 中所有不是类契约的操作,因为它们对分布在不同包中的类之间的通信没什么作用。

图 1. Item 类

步骤 4:标识可能的域包

一旦标识了类契约,您就可以开始标识它们提供的可能的域包和服务了。一个域包是一个互相协作以支持可以被认为是黑盒的内聚契约集的类集。基本思想是每个域包将提供一个或者更多的 Web 服务给其它域包、应用程序,甚至可能是外部系统。表面上看来,域包好象很简单,但是因为它们封装了许多类的行为,所以它们的内部通常相当复杂。

关键目标之一就是把设计组织进几个包,这样可以减少它们之间的信息流量。包之间传递的任何信息(以 Web 服务调用的形式或者以来自那些调用的结果集的形式)都代表着可能的网络流量。因为希望最小化网络流量以改善应用程序的响应时间,因此,您想要以这样的一种方式设计域包,这种方式使大部分信息流在包内发生而不是在包之间发生。

为了确定一个类是否属于一个域包,您需要分析它确定分布类型所涉及到的协作。服务器类接收消息但是不发送它们;客户机类发送消息但是不接收它们;客户机/服务器类既发送又接收消息。图 2 呈现了一个高级 UML 协作图,这个图中消息流用箭头指示。每个箭头可以代表几个独立的消息,对象的分布类型用标记 c、s 和 c/s 指示。

图 2. 简化的协作图

一旦标识了每个类的分布类型,您就可以开始标识可能的域包了。有这样一条经验可以遵循,服务器类属于一个域包并且常常会形成它们自己的域包,因为它们是应用程序里消息流的最后一站。在图 2 中您可以看到 Territory 类是一个服务器类,插图把它描述为一个可能的域包(用虚线指示)。这个类层次结构(记住,我们更早的时候强调过此层次结构)上的调用量可能太低以致无法激发您把 Territory 部署到它自己的服务器(或者服务器站)上的热情。

如果您有一个域包,这个域包是唯一的一个其它类或者域包的服务器,您可以决定合并这两个域包。在图 2 中,您可以看到在 Address 和 Territory 之间这类关系的一个示例,表明您可能想把它们合并成一个单独的域包而不是把它们当作两个独立的组件。在选择是否把这些类合并成一个单独的包时,您也可能想考虑其它的问题:

  • 安全性:您想隐藏 Territory 的存在吗?
  • 性能:在它自己的机器上有 Territory 会提高整个应用程序的速度吗?
  • 可伸缩性:您需要支持将来对 Territory 的调用次数快速增长吗?

另一条经验就是客户机类并不属于一个域包。客户机类仅仅生成消息但是不接收它们,然而域包的目的就是响应消息;所以,客户机类没有什么可以添加到包所提供的功能里去。在图 3 中,Shipment 是一个纯粹的客户机类并没有分配到一个可能的域包。

另外,当两个类频繁协作时,指出它们应该被放在同一域包里以减少在 Web 服务调用中对信息进行编组所使用的开销。当交互涉及到大对象(作为参数传递或者作为返回值接收)时特别是这样的;通过把这样的两个类包含进相同的域包里,您减少了它们之间可能的网络流量。最基本的想法就是高度耦合的类要合在一块。

一个相关的启发就是客户机/服务器类属于一个域包,但是可以有几个它可能属于的域包。这就是您需要考虑额外问题的地方,例如进出类的信息流以及添加新类如何影响每个组件的内聚度(事物的各部分在一块有意义的程度)。一个基本的设计规则就是,一个类只要存在并去完成一个域包的目标,那么它就应该是该域包的一部分。虽然在某种类型的 Order 包里包含 Item 类有意义,但 item 是 order 外部的重要实体也同样有意义 — 例如,一个库存控制系统显然对 item 感兴趣。所以,您可能想把 item 和 order 概念放在不同的包里。一个更好的安排会把 Item、ShippingContainer、ContainerType 和 ShippingRestriction 放在相同的包里,因为它们都封装了与 item 有关的任务。

最后,您还应该通过考虑可能的改变对类的影响而完善把它们分配到域包的分配工作。做到这一点的一种方式就是开发一个描述对系统实现的要求的可能改变的改变案例。(为获得更多有关改变案例的信息,请阅读 Designing Hard Software: The Essential Tasks 和 The Object Primer, Second Edition: The Application Developer's Guide to Object Orientation。在下面的参考资料中可以找到这两本书的链接。)如果您知道某些类可能改变,那么如果在改变确实发生时限制改变的范围有意义,您就可以选择在一两个组件中实现它们。

图 3 描述了 SWA Online 系统最后的包图,给包取的名反映了它们包含的类集。这个图的一个不理想的功能是 Customer 和 Order 包互相依赖,像这样的循环依赖增加了部署和维护的工作量,暗示着您可能要重新考虑两个包之间的交互或者甚至合并它们。然而到现在,我们仍坚持我们的包图。

图 3. SWA Online 的包图

我们接下来的几步就是标识每个包提供的 Web 服务和那些服务的签名。这些将是这一系列的下一部分的主题。到那时,您应该继续把这里概述的技术应用到自己的应用程序中。

 

组织简介 | 联系我们 |   Copyright 2002 ®  UML软件工程组织 京ICP备10020922号

京公海网安备110108001071号