级别: 初级 Chris Brandin, 首席技术官, NeoCore
2003 年 8 月 01 日 学习使用 XML 进行信息建模时实现良好文法和样式的指南。本文摘录自 XML Data Management(ISBN 0-201-84452-4, copyright 2003. All rights reserved.)一书的第 1 章。经 Addison-Wesley Professional 允许得以发布。
注:
developerWorks 编辑
Akmal Chaudhri参与了
XML Data Management 一书的共同编辑工作。
最初使用 XML 时,主要将它视为一种数据交换标准。此后,它的应用范围越来越广 ― 甚至充当象 Microsoft 的 .NET 这样的开发和部署平台的核心。XML 逐渐成为对信息系统组件建模的方法,并且这些组件根据用 XML 表示的信息自动构造自身。这体现了 XML 的真正潜力 ― 一次性用 XML 对整个应用程序的行为建模的能力,而不是象其它方法那样对应用程序的每个组件重复建模。
在 XML 被用作由原有系统管理的数据的容器时,构建文档时只考虑文法就足够了。既然现在 XML 不仅仅用于表示数据,那么就还要考虑文法和样式,这一点很重要。显然,正确的文法是解析器完全能够接受 XML 文档所必需的条件。良好的文法可以确保一旦接收了 XML 信息,无需过多的应用程序方面的专门(和冗余的)领域知识就可以有效地解释该信息。良好的样式可以确保良好的应用程序性能,尤其在涉及到存储、检索和管理信息时。
正确的 XML 文法已得到充分的理解和文档记录,所以本文将不讨论该主题。这一章不讨论如何构建 XML 模式或 DTD,因为它们在别处也有良好的文档记录。本章旨在作为一份实用指南,告诉读者如何在用 XML 对信息建模时实现良好的文法和样式 ― 这将转化为用最少的编程工作即可构建性能较好的灵活的应用程序。文法常被认为非对即错。是的,存在“错误的”文法这种说法;但除此之外,还有好文法和糟糕文法之分 ― 以及介于两者之间的文法。不存在错误的样式,只有好与坏以及介于两者之间的样式,这是可以论证的。
作为信息域的 XML
XML 允许我们以自然和直观的方式对信息系统建模。这是因为 XML 使我们能够以更符合我们做事的方式来表示信息。我们现在有的信息建模的机制允许我们描述想做的事情的特征,而不是如何去做它。在反映现实世界运作方式方面,XML 确实比它之前的数据建模机制做得更好。XML 为信息建模提供了许多功能强大的能力。
-
异构性:每条“记录”可以包含不同的数据字段。现实世界并非整齐划一地被组织成表、行和列。能够不受限制地按信息存在的方式表示信息好处很大。
-
可扩展性:可随意添加新的数据类型,不需要事先决定。这使我们能够利用更改而不是避免或更改。
-
灵活性:数据字段会在大小和配置方面随情况的变化而变化。XML 对数据没有限制;每个数据元素的长短按需要决定。
XML 也是自描述和信息完整的;应用程序可利用这一特性通过少量必要的编程或无需编程来自动构建自身。诸如 BEA、TIBCO 和 Microsoft 这样的公司提供了用最少的工作构建应用程序的框架,这些框架使用 XML 作为表示信息的基础。在这样的环境中,XML 成为了通用的信息构造工具,其中不再需要将系统组件作为离散的功能部件单独编程。NeoCore 提供了一个 XML 管理系统(XML Management System,XMS),该系统整合了完全透明的持久性机制,不需要单独的数据库设计过程、索引建立指令或用例预定义。而且,NeoCore 的 XMS 发扬了 XML 的特性 ― 异构性、可扩展性和灵活性,正是这些特点使得 XML 可以象信息域一样强大。存储、检索和管理信息所需的全部都可以用 XML 表示,查询可以用 XPath 或 XQuery 模式表示。这对于加快应用程序开发效率有深远影响,在必须进行更改时尤为突出。当我们构建以 XML 为中心的系统时,我们通常可以通过修改底层 XML 来适应变化,而且信息系统组件无需通过重新编程就可以相应地调整自己。
XML 如何表示信息
XML 使用四个基本组件表示信息 ― 标记、属性、数据元素和层次结构。每个组件都有专门的用途;每一个都代表信息不同的“维”。为了说明这些基本组件,我们将使用处理色度计(用三基色法读数测量颜色的设备)读数的应用程序中一段简单的 XML 片段。
在清单 1 中用
粗体字体表示数据元素。在 XML 中,数据元素等价于传统意义上的“数据”。如果我们只抽取数据元素,会得到“
0, 255, 255”,如果您不知道数据定义是什么,那么这种数据毫无意义。XML 通过添加标记(在该清单中以正常字体表示)给数据添加上下文,从而赋予它意义。标记描述了数据元素是什么。属性(在该清单中以
斜体
表示)告诉我们关于数据元素的信息或如何解释它们。色度计可以用不同分辨率表示 RGB 三基色法的值。例如,如果以 16 位分辨率获得读数,则值“
0, 255, 255”代表非常暗的青色,而不是纯青色。因此,我们需要“
resolution=8”属性来正确地解释清单 1 中的 RGB 读数值。
清单 1. 简单的 XML 片段
<colorimeter_reading>
<RGB
resolution=8>
<red> 0 </red>
<green> 255 </green>
<blue> 255 </blue>
</RGB>
</colorimeter_reading>
|
现在,我们有了数据(数据元素),知道它们是什么(标记),而且知道如何解释它们(属性)。最后要做的就是确定如何将所有内容串起来,这就要用到层次结构。到目前为止,我们已经显式地表示了信息的三维。最后一维(所有内容如何关联)是以空间形式暗示的。这表示许多我们要知道的东西都包含在我们对 XML 信息组件的排序方式中。为了给出数据的含义,必须提供完整的上下文,而不仅仅是紧邻的标记或属性。例如,如果我们只说“
red=0”,这没有多大意义,因为我们没有提供足够的上下文。如果我们包含层次结构中位于读数“0”之前的所有标记,就能得到一个更完整的上下文:“
<colorimeter_reading><RGB><red> 0 ”。尽管我们对数据元素表示什么以及它的值有了全面的理解,但在如何解释值上仍有点不明确。属性“
resolution=8”属于标记“<RGB>”。因为“<RGB>”是上下文的一部分,所以属于该标记的任何属性(或所有属于这样的标记 ― 在上下文中针对那件事的任何标记 ― 的属性)都起作用。现在我们也知道了如何解释数据元素的值。相关信息是用层次结构中位于不同级别的兄弟元素来表示;其结果是,层次结构告诉我们数据元素是如何相互关联的。
XML 中的模式
为了使用 XML 有效地进行信息建模,我们必须了解如何标识这一与生俱来就能表示信息的自然的模式。首先,我们必须确定是否正确地使用了 XML 元素。为此,我们将分析清单 2 中所示的 XML 片段。
清单 2. XML 片段示例
<colorimeter_reading>
<device> X-Rite Digital Swatchbook </device>
<patch> cyan </patch>
<RGB
resolution=8>
<red> 0 </red>
<green> 255 </green>
<blue> 255 </blue>
</RGB>
</colorimeter_reading>
|
我们检查每个数据元素并考虑以下问题:
- 这是数据吗,或者实际上它是元数据(关于另一个数据元素的信息)吗?
我们检查每个属性并考虑以下问题:
- 这个属性能告诉我们有关数据元素的信息,或描述如何解释、使用或显示数据元素吗?
- 这个属性确实是元数据而实际上不是数据元素吗?
- 它适用于其作用域内的所有数据元素吗?
我们检查每个标记并考虑以下问题:
- 这个标记有助于描述其作用域内的所有数据元素是什么吗?
我们检查已创建的分组(兄弟关系)并考虑:
- 组内的所有成员都以父节点所描述的方式相关吗?
- 兄弟之间的关系是明确的吗?
如果对前面任何一个问题有“否定”答案,那么我们需要以其它方式对这个有缺陷的组件进行数据类型转换。
在确保用适当的 XML 组件表示了信息之后,我们检查所有的内容是如何联系在一起的。为此,我们从这个 XML 片段创建一个信息上下文列表。要做到这一点,只需获取每个数据元素并写下在它之前的每个标记和属性。产生的行给出了 XML 片段中所包含的信息项的平面视图。清单 2 中示例 XML 片段的上下文列表如清单 3 中所示。
清单 3. 示例 XML 片段的上下文列表
<colorimeter_reading><device> X-Rite Digital Swatchbook
<colorimeter_reading><patch> cyan
<colorimeter_reading><RGB
resolution=8><red> 0
<colorimeter_reading><RGB
resolution=8><green> 255
<colorimeter_reading><RGB
resolution=8><blue> 255
|
如果将这些行转换成用文字表示,我们可以看到每个信息项和它的上下文都有意义,并且在上下文中是完整的:
- 该色度计读数来自 X-Rite Digital Swatchbook。
- 该色度计读数针对颜色为青色的一片。
- 该色度计的其中一项读数是 RGB 红,值为 0(以
8 位表示)。
- 该色度计的其中一项读数是 RGB 绿,值为 255(以
8 位表示)。
- 该色度计的其中一项读数是 RGB 蓝,值为 255(以
8 位表示)。
接下来我们检查由标记层次结构所暗示的分组:
- “<colorimeter_reading>”包含“<device>”、“<patch>”和“<RGB>”(加上其子元素)。
- “<RGB>”包含“<red>”、“<green>”和“<blue>”。
“<colorimeter_reading>”表示根标记,因此其它所有内容显然与它有关。层次结构所暗示的另一个唯一的分组被归到“<RGB>”这一类下面。这些是实际的读数,也是唯一的项,因此,十分明确,它们在逻辑上关联。
最后,我们检查每个属性的作用域:
- “resolution= ”的作用域中有“<red>”、“<green>”和“<blue>”项。
“
resolution=8”在逻辑上应用于其作用域中的每一项,对其作用域以外的项则不适用,这样它得以正确地应用。
自我构造的 XML 信息系统(譬如 NeoCore XMS)将使用 XML 的结构和 XML 所包含的自然模式来自动决定对什么建立索引。简单查询是通过直接查找完成的。复杂查询是通过组合直接查找和对所选父节点的汇聚及目标子串搜索来完成的。使用 NeoCore XMS,数据库设计或索引建立指令就不再是必需的 ― XMS 的行为完全由发布给它的 XML 文档的结构来驱动。通过推断决定索引项,并且根据 XML 文档中包含的这种自然模式来构建索引项。NeoCore XMS 根据以下规则创建索引项:
- 为每个数据元素创建索引项。
- 为每个数据元素的所有完整标记上下文创建索引项 ― 即,并置数据元素前的每个标记。
- 为前两项(标记上下文加上数据元素)的并置创建索引项。
对于清单 2 中的 XML 片段,将以下项添加到模式索引中(实际上,该清单不完整,因为还创建了部分标记上下文索引项,但对这些项的讨论超出了本章的范围):
-
X-Rite Digital Swatchbook
-
cyan
-
0
-
255
-
255
- <colorimeter_reading><device>
- <colorimeter_reading><patch>
- <colorimeter_reading><RGB><red>
- <colorimeter_reading><RGB><green>
- <colorimeter_reading><RGB><blue>
- <colorimeter_reading><RGB
resolution=8
>
- <colorimeter_reading><device>
X-Rite Digital Swatchbook
- <colorimeter_reading><patch>
cyan
- <colorimeter_reading><RGB
resolution=8
><red>
0
- <colorimeter_reading><RGB
resolution=8
><green>
255
- <colorimeter_reading><RGB
resolution=8
><blue>
255
项 1-5 只是数据,项 6-11 只是标记上下文,项 12-16 既是数据也是标记上下文。
这时,考虑 XML 文档的结构将如何影响性能是很重要的。因为从 XML 本身推断出的固有模式可用于自动构建数据库,所以这些模式匹配相似查询的程度对性能有很大的影响,在以数据为中心的应用程序(其中不需要处理整个 XML 文档即可访问单个数据元素或子文档)中尤其如此。
常见的 XML 信息建模错误
我们可以用其它完全可以接受的方法方便地排列前一节中的 XML 片段。排列信息有许多在文法上正确但不合适的方法。创建 XML 文档时常见的错误有:
- 描述数据元素是什么的上下文不充分(不完整的标记用法)
- 描述如何解释数据元素的指示信息不充分(不完整的属性用法)
- 将属性用作数据元素(不正确的属性用法)
- 将数据元素用作元数据而不是使用标记(通过使用名称/值配对的间接方式)
- 不必要、不相关或冗余的标记(糟糕的层次结构构造)
- 对数据元素解释毫无帮助的属性(糟糕的层次结构构造或属性的误用)
这些错误损害了 XML 的能力和有效性。花在进行良好信息建模上的时间将随着应用程序其它组件的开发而得到成倍的补偿。我们可以将大量精力投入到 XML 文档中,这意味着我们不必一再地在每个系统组件上耗费精力。
因为 XML 非常灵活,所以很容易滥用它。有时候,说明如何做某事的最好方法是举反例。我们看到的许多 XML(如果不是大多数的话)其设计都不是很好。设计具有良好文法和良好样式的 XML 并不困难,而且这样做从长期来看最终将节省大量时间和精力 ― 更别提它将对性能有怎样的影响。接下来的几节列举了几个构造不佳的 XML 片段。
用作数据元素的属性
这可能是最常见的 XML 误用。属性应当用来描述如何解释数据元素,或描述关于数据元素的信息 ― 换句话说,属性是一种元数据。它们常用来包含数据元素,而这与属性的用途背道而驰。
清单 4 根本没有包含表示读数的数据元素;属性不对任何内容起作用。不起作用的属性显然没有描述如何解释某一元素。
清单 4. 没有数据元素的 XML
<colorimeter_reading>
<device> X-Rite Digital Swatchbook </device>
<patch> cyan </patch>
<RGB
resolution=8 red=0 green=255 blue=255 />
</colorimeter_reading>
|
如果我们检查每个属性,特别是数据部分(等号右边的部分),就可以确定它们是否真的表示数据或元数据:
-
resolution=8:
这是真的属性,因为值 " " 本身不表示任何东西;它更可能是用于解释数据元素的指示信息,因此它是元数据。
-
red=0:
显然,这的确是数据,因为它是色度计的读数;此外,为了正确地解释,它需要“
resolution=8
”属性。该属性没有告诉我们如何解释数据 ― 它是数据。因此,应将它重新构造为标记/数据元素对。
-
green=255,
blue=255:
前面对“
red=0
”的分析在此也适用。
用作元数据的数据元素
这通常是仿效关系数据库中可扩展性的结果。数据库设计人员不会分别创建代表不同字段的列,而是创建了两个列:一个用于字段类型,另一个用于字段内容。这基本上等于用数据元素字段表示元数据,如清单 5 所示。
清单 5. 用作元数据的 XML 数据元素
<colorimeter_reading>
<device> X-Rite Digital Swatchbook </device>
<patch> cyan </patch>
<RGB>
<item>
<band> red </band>
<value> 0 </value>
</item>
<item>
<band> green </band>
<value> 255 </value>
</item>
<item>
<band> blue </band>
<value> 255 </value>
</item>
</RGB>
</colorimeter_reading>
|
如果我们将该文档分解成信息上下文,则会得到清单 6。
清单 6. 清单 5 的信息上下文
<colorimeter_reading><device> X-Rite Digital Swatchbook
<colorimeter_reading><patch> cyan
<colorimeter_reading><RGB ><item><band> red
<colorimeter_reading><RGB ><item><value> 0
<colorimeter_reading><RGB ><item><band> green
<colorimeter_reading><RGB ><item><value> 255
<colorimeter_reading><RGB ><item><band> blue
<colorimeter_reading><RGB ><item><value> 255 |
将清单 6 翻译成文字后大致如下:
- 该色度计的读数来自 X-Rite Digital Swatchbook。
- 该色度计读数针对颜色为青色的一片。
- 该色度计的其中一项读数是 RGB 的红色频带。
- 该色度计的其中一项读数是 RGB,值为 0。
- 该色度计的其中一项读数是 RGB 的绿色频带。
- 该色度计的其中一项读数是 RGB,值为 255。
- 该色度计的其中一项读数是 RGB 的蓝色频带。
- 该色度计的其中一项读数是 RGB,值为 255。
最后六行的上下文不很明确。第 3、5 和 7 行不包含任何读数;它们包含关于其后一行的元数据。第 4、6 和 8 行没有充分地描述所包含的读数;它们包含的信息不完整且模糊。事实上,第 6 和 8 行完全相同,尽管它们表示的读数具有不同含义。
这通常是仿效关系数据库中可扩展性的结果。数据库设计人员不会为不同的数据结构构建单独的表,而是通过使用名称/值对为许多不同的数据结构构建一个表。这反映了不必要的元数据间接方式和不合适的数据元素分组,对性能的损害(因为本应是直接查询却变成了连接)和可靠性的损害(因为分组比较模糊)。清单 7 显示了这一点。
清单 7. 名称/值对的使用
<colorimeter_reading>
<device> X-Rite Digital Swatchbook </device>
<patch> cyan </patch>
<mode> RGB </mode>
<band> red </band>
<value> 0 </value>
<band> green </band>
<value> 255 </value>
<band> blue </band>
<value> 255 </value>
</colorimeter_reading>
|
如果我们将该文档分解成信息上下文,则会得到清单 8。
清单 8. 清单 7 的信息上下文
<colorimeter_reading><device> X-Rite Digital Swatchbook
<colorimeter_reading><patch> cyan
<colorimeter_reading><mode> RGB
<colorimeter_reading><band> red
<colorimeter_reading><value> 0
<colorimeter_reading><band> green
<colorimeter_reading><value> 255
<colorimeter_reading><band> blue
<colorimeter_reading><value> 255 |
翻译成文字,清单 8 成为:
- 该色度计的读数来自 X-Rite Digital Swatchbook。
- 该色度计读数针对颜色为青色的一片。
- 该色度计的读数是 RGB 方式。
- 该色度计的其中一项读数是红色。
- 该色度计读数的值为 0。
- 该色度计的其中一项读数是绿色。
- 该色度计读数的值为 255。
- 该色度计的其中一项读数是蓝色。
- 该色度计读数的值为 255。
最后六行的上下文不很明确,第 3 行只表示上下文。第 3、4、6 和 8 行不包含任何读数;它们包含关于其后一行的元数据。第 5、7 和 9 行根本没有描述它们所包含的读数;它们包含的信息不完整且模糊。事实上,第 7 行和第 9 行完全相同并且属于同一组,尽管它们表示的读数有不同含义且应属于不同组。我们可以添加标记以将读数元素封装到组中,以使频带和读数值明确地相互关联。但我们首先应确定每个数据元素是否真的表示数据。如果我们检查数据元素,就可以确定它们是真的表示数据还是元数据,以及它们是否有足够的上下文:
-
X-Rite Digital Swatchbook:这显然是数据。
-
cyan:这显然也是数据。
-
RGB:尽管理论上可以把它看作数据,但它本身没有多大意义。另外,需要有它才能理解它后面的数据元素的含义。
-
red、
green和
blue:仅从理论上考虑,它们也是数据。它们也缺乏足够的上下文。例如,红色频带中的色度计读数可以表示其它一些东西。
-
0、
255和
255:它们是实际的色度计读数;它们显然是数据。然而,它们几乎没有关键的上下文 ― 即它们所表示的颜色方式和颜色频带。
设计 XML 的极简单方法
与传统数据建模相比,XML 信息建模的一大优势在于它更直观地模拟了现实。由于这一点,设计 XML 文档的一种非常简单的方法产生令人惊奇的好结果。事实上,它将比许多(如果不是大多数的话)“业界标准”XML 模式产生更好的结果。忘掉您将使用计算机来管理信息 ― 实际上,忘掉您所知道的与计算机有关的所有事情。想象一下,您将用手工管理信息,并相应地设计简单的表单。首先,将表单预先打印的部分变成标记;其次,将要填充的部分变成数据元素;然后,将类似于单位的东西变成属性。显然,这样做不会产生完全优化的结果,但它很有用 ― 而且是入门的好方法。
让我们看一个简单的示例 ― 电话号码簿。我们先从人工输入表单着手。
电话簿清单
| Name: | John A. Doe | | Address: | 123 Main Street | | City: | Pleasantville | | State: | Maryland | | Zip Code: | 12345 | | Telephone: | (999) 555-1234 |
如果我们将其直接转换成 XML 文档(空格变成下划线),则会得到清单 9。
清单 9. XML 格式的电话簿清单
<Telephone_Directory_Listing>
<Name> John A. Doe </Name>
<Address>123 Main Street </Address>
<City> Pleasantville </City>
<State> MD </State>
<Zip_Code>12345 </Zip_Code>
<Telephone> (999) 555-1234 </Telephone>
</Telephone_Directory_Listing>
|
现在我们做一些小的改动。首先,我们将姓名分为名、中名缩写和姓,并将它们放在一组。我们还将把地址放在一组,并将电话号码和区号放在另一组。分隔字段(如姓名)使得将某些部分作为单独查询项使用成为可能,这些单独查询项可用于直接查询而无需扫描字段内的部分内容。这在进行对诸如“John Doe”而不是“John A. Doe”的查询时将显著地提高性能。生成的 XML 如清单 10 所示。
清单 10. 更改后以 XML 表示的电话簿清单
<Telephone_Directory_Listing>
<Name>
<First> John </First>
<MI> A. </MI>
<Last> Doe </Last>
</Name>
<Address>
<Street>123 Main Street </Street>
<City> Pleasantville </City>
<State> MD </State>
<Zip_Code>12345 </Zip_Code>
</Address>
<Telephone>
<Area_Code> 999 </Area_Code>
<Number> 555-1234 <Number>
</Telephone>
</Telephone_Directory_Listing>
|
清单 10 中的 XML 文档可以很好地作为基本电话簿。当考虑到可能要将附加信息(附加的地址栏以及附加的电话号码等)添加到某些清单时,记住 XML 是可扩展的这一点很重要;仅在必要时才添加字段 ― 而不是全局地针对所有清单。
许多企业基本上都是由表单驱动。例如,制药业的临床试验在可以设计管理信息的计算机系统之前先从必须经过核准的表单开始。因为可以非常轻松地将表单转换为 XML,所以现在构建主要由业务目标(以直观方式表示的)驱动的,而不是通过抽象的计算范例驱动的系统成为可能。
结束语
关于 XML 和构建在它之上的新一代工具,最有前景的一点是我们可以构建由单一信息模型(而不是由多个数据模型来解决每个应用程序功能)驱动的应用程序。我们可以通过更改底层 XML 而不是通过更改代码来改变应用程序的行为和功能。此外,我们可以通过更改表示信息的方法来优化性能。即使在没有完全利用 XML 作为中心信息模型的环境中,为了可读性和可维护性而设计良好的 XML 也是很重要的。有效地构建良好的应用程序需要我们不仅学习如何正确地使用 XML,还要学习如何将它用好。
参考资料
关于作者  | |  | Chris Brandin 是 NeoCore, Inc. 的首席技术官。Brandin 是 NeoCore 的技术和产品的发明者和首席架构设计师,他还是并行处理计算方面的开创者。在进入 NeoCore 工作之前,他是 Business Operating Systems, Inc. 的 CEO,在那里他开发了纽约证券交易所和其它交易所使用的计算机系统。他还设计了 PC I/O 总线管理的控制集成电路、音乐合成器硬件和软件以及由 Symantec、NEC、Alcatel 和其它公司销售的其它产品。Brandin 为 Motorola、AT&T、Bell labs、Belcore、Wang、IBM、Intel 以及其它公司在专利方面的问题提供咨询。 |
对本文的评价
|