本页内容
![]() | 什么是 Fiefdom? |
![]() | 安全的网关 |
![]() | 体系结构模式 |
![]() | Emissary |
![]() | 根据业务规则进行验证 |
![]() | 与客户端松散耦合 |
![]() | 基于 XML 的 Web 服务 |
![]() | 根据用法设计 XML 数据集 |
![]() | 使用自定义实体类 |
![]() | 将实体对象映射到数据库 |
![]() | XML 数据集从不连接到数据库 |
![]() | 小结 |
没有人会与世隔绝,对于开发人员来说更是如此。软件架构师、设计人员和开发人员需要相互交流才能取得成功。应用程序也一样。过去我们将应用程序视为独立的 (self-contained) 应用程序。大型机应用程序以及小规模的 PC 应用程序都可以包含它们所需要的一切。它们控制用户界面、业务逻辑和数据,无需顾及应用程序以外或者让外界程序介入。
但现在就不再是这样了。我们今天构建的应用程序通常需要与现有软件相集成,而且这种趋势还在继续。另外,越来越多的应用程序不再控制用户界面,原因有二:一是用户界面在其他地方设计;二是用户界面通过 Internet 访问应用程序。
很显然,在构建和设计应用程序时,我们应该考虑跨计算平台集成的这种需求。幸运的是,我们有一个体系结构解决方案。面向服务的体系结构 (SOA) 将应用程序看作可通过 Internet 发现和访问的服务,而不管使用者在什么平台上运行以及使用什么编程语言来创建使用者。换句话说,服务提供的是与平台和语言无关的服务。Web 服务只是其中一个例子。
什么是 Fiefdom?
服务应该是完全自治的应用程序。但它不是个孤岛,因为您可以让它为您执行服务。但它的自治性表现在它完全控制其数据并拒绝外界直接访问该数据或直接访问其对象。访问其资源的唯一方式是向其发送消息以请求为您执行任务。如果它不喜欢您的请求方式或者不喜欢您,它就会拒绝执行服务。没有人会以它的名义做决定,它自己做出决定。
Microsoft 架构师 Pat Helland 曾经是自治应用程序第一人,但他已不再是该领域的唯一一人。这是一个合理的概念,我们将在本专栏中向您进行介绍。Pat 使用术语“fiefdom”来描述自治应用程序,这里我们还将使用这个术语。
fiefdom 保存、管理、监视和保护它的主要资源 — 数据。fiefdom 的数据可以保存在不同类型的数据源中,但在大多数情况下,结构化数据保存在关系数据库中,例如 Microsoft SQL Server。fiefdom 从不允许任何人或任何工具从 fiefdom 外部直接获得其数据。fiefdom 可能允许它外界的某些人访问其数据,甚至以这些人的名义进行操作,但只有该 fiefdom 才能直接访问。
fiefdom 不允许外部程序对其任何数据进行加锁。因此,您必须考虑的是使 fiefdom 成为快照的所有数据,而不是实际内容。对于大多数实际用途,当前数据只存在于 fiefdom 内部。您不能期望从 fiefdom 接收到的数据是当前数据,更别说是接收那一刻的数据。fiefdom 内部一些事务可能在您接收到该快照之前已经更改了其数据源中的数据。
大多数 fiefdom 仍坚持管理它们自己的事务。fiefdom 不应该允许外部程序控制在 fiefdom 内运行的事务或部分事务。然而,fiefdom 可以公开服务来管理受外部方控制和协调的部分事务,只要这部分事务是补偿事务。fiefdom 独立控制这部分事务,但是将它作为提供给客户端的服务,这就是事务协调器。
补偿事务的一个例子是遵守 Web 服务事务规范第二部分:业务活动 (BA) 的事务。这样的事务有两个不同的方案。标准方案执行由事务指定的操作。补偿方案执行另一组操作,其作用是删除 fiefdom 中已完成事务的影响。补偿方案由本身可能为另一个 fiefdom 的事务协调器调用。调用补偿方案的情况是 fiefdom 已经完成了它的部分事务,但后来协调器发现事务的其他部分执行失败。在这种情况下,必须消除 fiefdom 运行并完成的部分事务所造成的影响,即使它不能回滚。对记录所加的锁已经释放,而其他事务可能已经改变其状态。补偿方案会消除这些影响。
并不是所有类型的事务都适合补偿。大部分 fiefdom 并不(也不应该)参与不存在可靠补偿方案的事务。
安全的网关
在某种程度上,面向服务的性质决定了面向服务的应用程序是安全的。让应用程序为您执行任何任务的唯一方式是向它发送消息,请求执行某一服务。执行还是拒绝请求取决于应用程序。
在许多情况下,应用程序会允许某人执行特定一个或一组服务。在这些情况下,Web 服务需要做的仅仅是将作业委派给适当的组件,然后将响应传递给客户端。在其他情况下,Web 服务可能需要验证用户的身份以确定他是否被授权执行该服务。这可以是仅在网关处针对 Active Directory 或 SQL 数据库进行简单的检查,也可以在应用程序的不同级别结合使用安全技术(例如模拟或委派)和基于角色的安全检查。请记住,必须始终注意拒绝服务攻击、试图入侵系统的黑客以及其他恶意威胁所造成的风险。
作为安全计划的一部分,Microsoft 已经发布了若干面向安全性的文档;其中最重要的一个文档是“Building Secure ASP.NET Applications”,现在已出版成书 (Microsoft Press, 2003)。您可以下载该文档 (Building Secure ASP.NET Applications:Authentication, Authorization, and Secure Communication) 也可以购买图书;它包含了有关如何为基于 Web 的应用程序构建深层安全性方面的大量信息。
体系结构模式
很显然,fiefdom 是一个体系结构模式。与其他模式一样,您作为一个软件架构师应该能够说出“我想我们应该将这个应用程序设计成一个 fiefdom”,同时您身边的人应该能够立即理解您在说什么。这就是模式的强大优势之一;它们有助于您讨论体系结构、在更为抽象的级别设计并使用通用词汇来加以设计。
显然,并非所有 fiefdom 都要以同样的方式架构。它们提供的一组服务会影响它们内部需要的组件类型,所以任何 fiefdom 都将在其内部实现中使用其他模式进行架构。例如,大部分 fiefdom 都将使用模式(如使用数据访问器)访问数据库;使用服务代理访问其他服务;使用数据传输对象通过各层传输数据,以及使用实体管理器来支持实体级业务规则。
Emissary
一些 fiefdom 也会为它的服务提供用户界面 (UI)。这样的 fiefdom 会使用一个 Emissary 模式 — 一个 Web Form emissary 或者一个 Windows Form emissary。Emissary 就像一个销售代理或者一个抵押经纪人。它可以向用户显示参考数据,允许用户从有效的可选方案中选择,而不是键入信息。Emissary 也知道 fiefdom 使用的许多业务规则,所以它可以帮助用户以一种 fiefdom 更容易接受的方式提出请求。
Emissary 也可以为用户保存会话状态。一个典型的例子就是 ASP.NET Web 窗体,它在多次调用期间为用户保存购物车,从而允许用户向购物车中已有的商品添加商品,这些商品一直保存在购物车中,直到购买过程完成。
然而,fiefdom 并不信任 emissary,甚至当 emissary 必须作为 fiefdom 的一部分查看时也如此。通过 emissary 传递过来的任何请求都要对其安全性、完整性和正确性进行评估,就像对传入时没有 emissary 协助的请求进行评估那么仔细。保险公司对销售代理引进的保险应用程序所进行的处理正是这种情况。使用 emissary 更多的是为客户端的利益着想,而非 fiefdom。
这样应该如何在 fiefdom 和其 emissary 之间分配工作呢?应该这样,fiefdom 必须处理共享数据,这意味着必须对写操作进行串行化以便保护数据的完整性。相反,emissary 可以处理参考数据的本地只读副本和每用户数据。因此,emissary 可以忽略同步问题。在非军事区 (demilitarized zone),emissary 通常一次只处理一个客户端,而 fiefdom 通常驻留在受保护的 Intranet 内,而且必须始终对所有客户端可用。
使用 Web 窗体,emissary 事实上可以进行扩展。而扩展 fiefdom 就相当困难,因为它处理的是共享数据,而共享数据必须始终对所有客户端可用。
因此,如果能够将更多的工作分配给 emissary,从而降低 fiefdom 的负载,则应用程序的可扩展性就会显著提高。您可以通过周密设计来实现这一点。让我们来看一看 Amazon.com。当您浏览它们的站点,将书和其他商品放入购物车时,所有工作都可以由 emissary 完成。只有当您点击“Submit”按钮进行结算时,您才需要用到 fiefdom。
但是要做到这一点,您必须缜密设计系统:划清 fiefdom 和 emissary 的界限,并使用性能和扩展增强器(例如缓存对象)。此外,您设计 fiefdom 的服务界面所采用的方式也必须使得 emissary 和 fiefdom 之间的交互最小化。
根据业务规则进行验证
有些发送给 fiefdom 的请求要求保存或删除数据。如果没有根据安全策略对请求本身并根据业务规则对其数据进行仔细验证,fiefdom 就不应该接受这样的请求。这就是业务规则和策略规则属于 fiefdom(由 Pat Helland 定义)并位于服务(由 SOA 定义)的原因。
与客户端松散耦合
fiefdom 与其客户端进行松散耦合。通常在客户端向 fiefdom 发送消息以建立连接之前,客户端和 fiefdom 之间并没有任何连接。fiefdom 处理该消息并发送响应给客户端。响应可以是请求操作的结果,也可以是对执行服务的拒绝。在任何一种情况中,都是只要 fiefdom 响应完毕,就断开客户端和 fiefdom 之间的连接,就像 HTTP 消息交换一样。正如 HTTP 一样,有多种方式可以在整个会话期间保持连接,但通常只限于一个完整的消息周期。
松散耦合对可扩展性而言是有好处的。它使得任何客户端连接到系统并使用系统资源的时间最少。松散耦合和一次性管理 Microsoft 事务处理服务 (MTS) 的设计,并随后纳入 COM+ 组件服务的所谓 .NET Enterprise Services 的原则相同。
基于 XML 的 Web 服务
隐藏在 SOA 背后的一个概念是位置透明,另一个是与计算平台和编程语言无关。Web 服务对于满足这些基础概念是十分理想的。由于 Web 服务为人所知的是它的 URL,而其实际位置可以更改(只要服务提供程序仍然指向该位置)。
由于 Web 服务与底层平台和编程语言完全无关,所以它只要求客户端理解 SOAP、XML 和所使用的传输协议。传输协议通常是 HTTP,但也可以是 SMTP 或任何标准的 Internet 传输协议。
有人会认为,以 Microsoft .NET 目标语言编写的任何 fiefdom 都应该通过 Web 服务公开其数据和功能。它并不需要将这些 Web 服务向外界公开;它们仅有的的直接客户端类型可以是 fiefdom 所熟知的 emissary。
fiefdom 内部应该构建在使 fiefdom 及其客户端之间松耦合的概念上。fiefdom 应该使用无状态服务组件,而不是状态对象集合。它传输和操作数据的形式应该尽量与它接收数据和传递给客户端的形式一样,也就是 XML 流形式。Microsoft .NET Framework 有助于这样做,因为它提供完整的基于 XML 的 API 来通过 fiefdom 传输数据以及向客户端公开数据。
这些 API 由 ADO.NET 的 XML 数据集组成。ADO.NET 的数据访问功能能够很容易地从任意多的数据库表中提取数据,并用数据填充 XML 数据集。您可以很轻松地将诸如多个组件间的数据集之类的内容传递给使用者,甚至是跨进程和机器界限传递。此外,因为包含在数据集中的数据可被视为容易使用的 XML 文档,所以将其用作 XML Web 服务响应非常方便。最后,尽管数据集包含 XML,要访问或操作其中的数据也无需使用 XML 语法。要访问属性,可以使用普通的面向对象语法,例如 TrainerData.Trainers(3).LastName。
我们有时候听到一些针对使用 XML 数据集来表示业务实体的争论,是这样说的:“我们不想向客户端公开数据库的结构。”其实,在大多数情况下,让数据库的结构影响 XML 数据集的结构是很不明智的。需要独立的用法就应该指明数据集的结构。您可以很容易地定义数据集的结构,无需跟数据库的结构有任何关连;在设计数据库之前定义好应用程序的所有数据集是很有可能的。
根据用法设计 XML 数据集
作为一个示例,您应该考虑属于比赛应用程序的“查看即将到来的赛程”用例,我们在前面的书籍和文章中建立了该应用程序。该用例的目的是显示即将到来的赛程列表。数据需要包含日期、赛道、城市和国家。
![MIissues0307DesignPatternsfig01](https://i-blog.csdnimg.cn/blog_migrate/789f2449efbb822f9d62ac3d2e4115b8.png)
图 1 XML 数据集
知道该用例需要的数据之后,我们就可以为它设计 XML 数据集了。图 1 显示了这种数据集的理想设计。我们可以看到,该数据集只包含一张表。RaceDayTable 数据表包含了用例需要的所有数据。该数据表中除了 RaceDayId 列之外,每一列都有显示给客户端的对应列,如图 2 所示。(该数据表中的 RaceDayId 列是为了让用户选择一个要显示的比赛日期,然后将该天的详细比赛信息显示在另一个页面上。)
![MIissues0307DesignPatternsfig02](https://i-blog.csdnimg.cn/blog_migrate/b0e4f6031a3e794d5488a2c966a2ed7c.png)
图 2 赛程详细信息
让我们来看一看数据来自何处。如图 3 所示(它显示了应用程序的数据库模型的一部分),要创建用例需要的一个并且仅一个数据集表,必须访问四个数据库表。
![MIissues0307DesignPatternsfig03](https://i-blog.csdnimg.cn/blog_migrate/dcc38c4fcce2ac724b7af2550c6c4502.png)
图 3 四个数据库表
如果有兴趣,我们再来看一个能将数据从四个数据库表移动到一个数据集表的存储过程,它使用一个 SQL SELECT 语句,并且只与数据库进行一次往返交互,如以下代码所示:
CREATE PROCEDURE GetFutureRacedays AS SELECT RacingProgramId As RaceDayId, Date, TrackName As RaceTrackName, CityName, CountryName FROM RacingPrograms As RP JOIN RaceTracks As RT ON (RP.RaceTrackId = RT.RaceTrackId) JOIN Cities As Ci ON (RT.CityId = Ci.CityId) JOIN Countries As Co ON (Ci.CountryCode = Co.CountryCode) WHERE Date > GetDate() ORDER BY RacingProgramId
从本示例可以看出,数据集的结构与数据库的结构是完全不同的。这是因为数据集是根据单用例的需要设计的,而数据库是使用正式的规则设计的,有助于防止使用数据时发生异常。
使用自定义实体类
让我们看一看如果开发人员不想向客户端公开数据库的结构,应该如何设计对象。他们可以选择按照一种常用的设计模式来设计代表所谓“域模型”的有状态对象。当然,域模型代表的是真实域的对象。在我们的比赛应用程序中,域模型可以包含诸如马、驯马师、赛马骑师和比赛等类。
另外,域数据的实际数据源也可以是数据库。这是大多数结构化数据存储的地方。显而易见,数据库模型近似代表域,因此它是个域模型。
这就是为什么在应用程序中主要由自定义实体类组成的实体类集与数据库中表示相同域的表集如此符合的原因。这也是为什么许多开发人员低估与类模型相反的实际数据模型的值的原因。
理想情况下,类域模型与相关域模型之间的一致性不应该如此接近;数据库应该适当标准化。以同样方式标准化内存内的实体类通常会生成大量实际上不需要的代码。这也会导致增加代码复杂性和减慢操作速度。
将实体对象映射到数据库
为什么内存内的实体类趋向于像数据库表那样标准化呢?这主要是因为经常用于相互映射内存内的对象和相关表的策略造成的。由于很少产生异常,建议采用基于内存的域模型的作者也会建议在基于内存的类和数据库表之间进行近似一一映射。
图 4 提供了在这样的情况下客户端所面对问题的简化实例。除了键和集合类,它几乎就是数据库表的复制品。不言而喻,它采用多个 SQL SELECT 语句来填充这四个实体对象。也很显然,与数据库进行四次往返交互比一次往返需要更多时间。而其好处仅仅是使客户端较少依赖于数据库。
![MIissues0307DesignPatternsfig04](https://i-blog.csdnimg.cn/blog_migrate/adf380242259b44214b6bf933a742893.png)
图 4 映射结果
应该明白,向客户端应用程序公开 XML 数据集并不等同于使客户端依赖于数据库。实际上,恰恰相反!但必须理解 XML 数据集才能充分利用它们。在定义它们的 XSD 方案时,应该将注意力放在它们的使用上;根本就不用考虑数据库的结构。
这一点在面向服务的应用程序中特别重要。面向服务的应用程序向客户端公开返回 XML 流或 XML 文档的编程接口。至少对于公开 Web 服务的面向服务的应用程序是如此。XML 文档的结构受其 XSD 方案控制,对于 XML 数据集也一样(它们其实就是 XML 文档)。
XML 数据集从不连接到数据库
一些开发人员可能还不知道 XML 数据集从不连接到数据库。它不能够连接到数据库。要在 XML 数据集和数据库之间移动数据,需要某类代理来将数据集表映射到关系表。ADO.NET 提供这样的对象,对于不同的数据库产品有不同的特色。它被称为数据适配器,只用于在数据集和数据源之间移动数据。数据集本身并不知道数据库的任何信息以及向数据集填充数据或将数据移到数据源的 SQL 语句的内容。
那为什么这一点会那么容易令人混淆呢?为什么这么多人相信使用数据集意味着依赖于数据库呢?原因可能是 XML 数据集在习惯叫法上像是用于传统 ADO、远程数据对象 (RDO) 和数据访问对象 (DAO) 的旧记录集的替代品。在所有这些模型中,记录集都直接连接到数据源(例如关系数据库)。唯一例外是 ADO 记录集,它可以断开与数据库的连接,然后再次连接。
XML 数据集与旧记录集完全不同。它们的目的是保留和传输数据快照,完全独立于可能从中提取数据的任何数据库并忽视它。
小结
下一次我们将着手介绍一个软件开发项目,考虑将其架构为 fiefdom。如果您的应用程序要公开用户界面(大多数应用程序都如此),则应该考虑将其架构为一组 emissary。Emissary 应该尝试尽可能在没有 fiefdom 的干预下完成自己的工作,除非万不得已。例如,它们应该缓存参考数据,应该保留会话状态而不借助 fiefdom。以这种方式构建的应用程序的可扩展性强于最大程度利用服务器资源(例如共享数据)的应用程序。
该策略有助于您将 fiefdom 设计成使用无状态组件。对于提高可扩展性和简单性,这是非常有用的。然而,您应该在万不得已的情况下才使用有状态组件。在大多数应用程序中,应该真正考虑使用 ADO.NET XML 数据集,将其作为数据传输器。再结合使用数据适配器,就可以完美地利用数据库了。它们透明地跨进程和机器界限工作,并且完全基于 XML。它们被设计为数据绑定,这在很多情况下是在 Web 窗体或 Windows 窗体中向用户显示数据的首选方法。
如果您想了解 Pat Helland 的 fiefdom 和 emissary 体系结构模式的更多内容,请亲自收听体系结构网络广播:Autonomous Computing:Fiefdoms and Emissaries;它非常值得一听。
将您对“设计模式”的问题和评论发送至 mmpatt@microsoft.com.
Sten Sundblad 和 Per Sundblad 联手在瑞典开展 Sundblad & Sundblad ADB-Arkitektur,这家公司的主要业务目标是 .NET 开发,具体讲就是体系结构与设计。他们共同编写了 Designing for Scalability with Microsoft Windows DNA (Microsoft Press, 2000) 和 Design Patterns for Scalable MicrosoftNET Applications (Sundblad & Sundblad, 2002)。有关 Sten 和 Per 的更多信息,可以访问 http://www.2xSundblad.com。您可以分别通过 stens@2xSundblad.com 和 pers@2xSundblad.com 与他们联系。