首先,你将了解对MTF的简要概述(它是什么,有哪些重要的用例,等等)。然后你将了解贯穿这篇文章的基本概念(映射关联以及映射语言的语法)。
本文实际上是一个阐明如何为一个具体例子书写规则的循序渐进的指南,而这个具体例子是指从一个UML(统一建模语言)转变为关联模型。每一步表达了一个新的映射规则,并解释了相关的概念;在需要的地方有类图描述规则。
指南结束于一个MTF安装工具的表达,它使你能够运行例子。最后,结论部分概括了本文中涉及的概念并推荐了一些其他资源。
在软件开发中使用建模
使用建模是软件工业的一个上升趋势,正如模型驱动工程的兴起和对象管理组织(OMG)最新的产品——模型驱动体系架构(MDA)——的发布所指出的。欲知更多信息,请参考本文后面的资源一章。模型转换在本文中起了关键作用,因为它能使你:
在2002年,作为Meta Object Facility (MOF) 2.0标准版的一部分,OMG提出了建立允许用户查询、建立并维护视图以及转换MOF模型的标准的建议。这一MOF 2.0标准被称为MOF 2.0 Queries/Views/Transformations (简称QVT)。在2004年11月,一些愿意遵从该标准的公司(包括IBM)开始合作定义QVT标准。当时的规约还需要大量的修订。如果需要关于OMG初始版本的更多信息,请参看本文末尾的资源部分。
什么是模型转换框架?
作为参与QVT标准的一部分,IBM已经开发了一个模型转换工具包的原型,称为MTF,现在可以从alphaWorks获得(见资源)。MTF实现了QVT的一些概念,并且是基于EMF的。它提供了一种简单的声明语言,可为定义模型之间的映射而用,另外为了完成转换还提供了一个转换引擎,它解释了映射的定义。
MTF的目的是通过支持递增的更新、双向的检查、调和以及可追溯性简化你开发转换工具的工作。最常见的相关用例包括:
MTF的外在形式是一组你可以在Eclipse或者IBM软件开发平台产品上部署的插件。它为EMF模型和一组帮助你书写、运行和调试映射规则的工具提供转换引擎。规则编辑器提供了基本编辑特性(比如语法检查)。调试器允许你一步步地跟踪规则的运行。映射视图使你能够在转换完成时监视转换的结果。
有趣的是,转换引擎并不是只期望在一个Eclipse环境内部运行的。它只依赖于EMF运行时库,因此它可以用于任何Java应用以及Eclipse插件中。
MTF基础
模型转换概念
术语模型转换背后的主要思想是根据预先定义元素间的关联,根据输入的其它模型产生模型。它假定为了使用一致性方式表达通讯,这些模型是用兼容的通用模型描述的。在QVT的作用域内,模型转换与MOF模型相关。MTF把这些概念应用于EMF模型。
一个MTF转换的结果是一组映射,它们把不同模型中的对象联系起来;一个与任何映射都无关的对象被认为是位于转换域外的。指定模型间的映射提供了定义转换结果的声明方式。比如说,如果一个映射需要目标模型中存在一个对象,转换引擎将试图创建该对象来满足映射的需要。
关联个体实例的映射也许不是指明一个转换最简明和方便的方式。你可能偏向于在类的级别上定义通讯,而不是为每一个实例定义。为了做到这一点,你可以指明关联相同类型的对象的映射,并把它们命名为关联。在MTF中的一个关系(也称为一个映射规则)定义了一种可应用于给定模型类的实例的映射类型。在某种意义上,一个关联的语义与一个限制十分相似。
下面的图1是一个例子,指明了两个模型(分别用红色和蓝色表示)之间的映射,其中包含了用于相关实例产生三个映射(m1, m2和m3,用白色表示)的两个关联(A2C和A2D)。
图1 两个模型间的映射的例子
MTF下的转换是用一种声明的方式定义的:你指明模型类间的一组关联,然后让MTF引擎使用关联作为输入完成转换操作。转换引擎的工作分为两个阶段,分别是映射和调和。在映射阶段,引擎评估关联,并通过在相关模型实例中进行迭代产生映射。在这一阶段的最后,一些映射可能与关联不一致。换句话说,不是所有关联要求的限制都被满足了。
调和试图通过创建缺失的元素,修改现有的元素,或者删除元素来满足所有关联。在某些情况下可能不需要调和(比如模型已经是一致的了,或者你只想检查模型的一致性而不想改变它们)。此外,调和不是永远可能的(如果从映射没有可引用的值调和就是不可能的)。这些概念现在看来有点抽象,但是在后面的章节中我们将给出详细的解释。需要记住的主要思想是下面这个过程:
关联 > 映射 > 映射 > 调和 > 更改的模型
关联的组织方式是符合模型的结构的。通常你会在每个模型类中找到至少一个关联,从联系顶级模型类的关联开始。与基于规则的系统相似,从一个关联中调用另一个关联是可能的,而且可以把映射的执行传递到有联系的模型类。这种机制称为通讯,它允许MTF通过把从顶级关联开始的所有通讯变为可到达的(直接或间接)横跨一个完整模型。
MTF支持一个灵活的转换模型,它可以接受多个模型,联系多个元素,并更新多个模型。关联没有指定评估的任何方向,它们可以在多个方向下执行,这在你处理双向问题时是很有用的。转换的方向(源和目的)实际上是在你调用转换引擎时被定义的。
为MTF编写规则
正如你可能已经猜到的,驱动转换的关联在MTF中起着重要作用。它们是用一种叫做关联定义语言(RDL)的语言来表达的,该语言被转换引擎分词和评估。基于结构化特性之间的通讯,RDL允许类之间的关联的定义和应用。比如,一个典型的关联会用一个匹配标志符属性联系两个类。语言的语法很简单,只包含几个关键词和标准布尔操作符。
关键词relate
允许使用表1中的格式定义关联。名字和形式参数都在关联中定义。在一些情况下你可以限制这些参数来过滤匹配的值。关联的主体通常包含需要用于匹配的值属性的通讯,这样可以传递转换的执行。基于类的EMF结构化特性可以通过查询匹配值查找属性。关键词equals
是指一个内嵌的通讯,它允许对字符串和其他原始类型的相等测试。
|
RDL语言尽管很简洁,却使你能够表达很多模型转换中使用的关键概念(比如,通讯,表达式,以及条件)。由于关联只定义元素间的通讯关系,它执行的上下文(换句话说,输入的实际参数)就十分重要了。不同的执行可能会产生不同的结果。下一节中出现的转换将有助于详细解释这些概念。
例子:一个UML到关联模型的转换
必要准备
这一节讨论如何在一个具体用例中使用MTF:一个UML到关联数据库(RDB)模型的转换。关于这个主题已经有很多讨论和若干转换算法。在本文中我们基于J. Rumbaugh et al.(参见资源)介绍的原则实现了一个部分映射。该映射的主要规则如下:
把类映射到表
映射关联到表
为了简单起见,这个例子做了几个简化假设。它不:
你将使用Eclipse.org UML2项目(参见资源)的UML 2.0通用模型的一个EMF实现。IBM® Rational® Software Modeler的UML建模工具和IBM® Rational® Software Architect的产品都是基于这个EMF通用模型的。
由于没有关联计划的标准通用模型,而且还是为了简单起见,我们决定基于EMF来定义我们自己的通用模型,我们称它为SimpleRDB见图2。这张图定义了一个包含一组表的计划,它们可以用简单键值限制联系起来(细节如下)。
另一个解决方案是平衡RDBSchema通用模型,该模型被用于实现IBM? Rational? Application Developer (IRAD) SQL工具的一部分。在这种情况下,从UML模型中产生的计划可以被IRAD工具消耗——比如说,为了产生SQL代码。这是基于MTF的一个很好的工具集成的例子。
图2 SimpleRDB通用模型
MTF运行在Eclipse 3.0或更高版本上,配合相应的EMF级别。它也可以安装于IRAD,Rational Software Modeler,以及其他IBM软件开发平台工具上。它作为一组插件被封装,你只需把它置于你的插件目录下(关于安装的更多细节请参见MTF程序员指南——见资源)。为了运行本转换例子,你还需要安装以下软件:
这个例子解释了RDL语言的基础,这将帮助你开始学习MTF。这里解释的很多关联用UML类图进行了说明。由于没有标准图形注释来描述MTF用到的概念(关联,通讯,条件),我们将使用一个变化的标准UML类图,我们向其中加入了我们自己的格式和限制注解。
在你的工作空间中建立一个项目,然后创建一个名为simplerdb.rdl
的新文件。使用MTF映射规则编辑器打开该文件(.rdl是编辑器默认的文件扩展名)。现在,你已经准备好编写你的第一个关联了,如表2所示。
步骤1:定义导入陈述
RDL文件以一系列导入陈述开始,它们被用来声明转换中涉及的EMF包。一个导入把一个URI包绑定到一个逻辑名,作为关联中指明的类的前缀,如表2所示。
|
这些逻辑名提供了与XML名字空间相似的一种唯一的识别机制。如果你不知道你确切需要导入哪些包的URI,映射规则编辑器提供了一个内容辅助特性,它列出了工作环境中注册的包。
在你的例子中,为了把UML模型联系到关联计划,你需要导入uml
和rdb
包。你还需要导入ecore
, ws
和util
实用程序包,它们被用来加载模型(将在下一步中作解释)并导入特殊结构。
步骤2:把UML2资源映射到SimpleRDB资源
转换从顶级关联开始,如表3所示。它为加载模型对象和在工作环境下运行转换提供了一个方便的机制(参见运行!一节)。如果你已经编写了Eclipse插件,你可能对类名IFile
比较熟悉。这里,你要处理的不是 org.eclipse.core.resources.IFile
类本身,而是它周围的EMF包装。
|
MTF的设计是对EMF模型进行转换,但是它也提供了改写非EMF类的方法。一种称为扩展的机制使你能够采用任何Java类并在转换引擎中运用用户定义语义进行操作。MTF包含一些对文件和EMF资源提供默认支持的扩展包,ecore
和ws
就是其中两个。并不一定非要使用这些包,但是它们确实在你定义变化时很有帮助。
你可以分开编写加载相关的EMF资源和对模型对象调用映射引擎的代码,但是在这个例子中所有任务的完成都是透明的。如果你对RDL的语法还不熟悉的话不要担心,在例子的下一步中我们将会加以详细解释。
RDL中没有定义用例或命名的规则。本例子试图遵循Java的命名规则按照自顶向下的方法组织关联(映射,表达式,以及条件)。
步骤3:把模型映射到计划
列表4表示的关联把任何给定UML模型映射到同名的一个计划。形式参数uml:Model
和rdb:Schema
被用于匹配标准;因此,只有当用于关联的值是Model
或者Schema
的实例时才会创建映射。
|
如果你仔细研究一下前面的关联,你就会发现它为了传播从EMF资源到它们的内容的转换是如何调用这一关联的。一个EMF资源定义了一个E对象的集合,这一集合在模型中是可获得的,并可以通过contents
属性访问。关联UmlModel2Schema
将同左面的资源(UML模型)的内容一起被例化。实际上,你永远有把关联应用到任何对象(或者一组对象)的能力。如果对象不符合类型标准,那么什么都不会发生。如果符合,对每一个符合的值的组合都会有一个映射被例化,然后转换将被传播到子元素。
关联签名主要关注UML模型和关联计划之间的映射,但是并不表达它们的内容应如何映射。用于联系模型元素的通讯是在关联体里的,当关联被应用时,转换引擎将搜索通讯——类之间的和UML模型内定义的关联,以及关联计划中定义的表——并为它们产生相关映射。
我们确信计划的名字与使用equals
内嵌通讯的模型的名字是相同的;而且Model
和Schema
类的名字属性是相同的。注意用于查询EMF对象的属性的基于点的语法。RDL中使用的属性名是基于EMF类的结构特性的。
在UML 2.0的通用模型中,类和关联是PackageableElement
的子类,而且通过一个称为ownedMember
的浏览属性从Model
类中是可以访问的。在你的转换中你使用这个属性来联系类/关联和关联计划的表。由于ownedMember
和tables
属性指的是一个集合,我们使用关键词over
来指出通讯将被用于集合所有的元素,而不是把集合本身当作一个对象。
在这个特定用例中,关联的执行将会在所有表和所有符合后面规则中解释的标准的用户之间产生一个衔接产品。注意到关键词match
指出了领域中的一些元素,比如列表4中的schema.tables
,被允许保持不匹配。为了防止没有带有前缀的匹配关键词的情况(比如列表4中的model.ownedMember
)出现,集合中所有类型符合的元素都必须通过关联UmlClass2Table
进行映射。
图3 关联UmlModel2Schema及其通讯
示例的这一步骤中表达的关联只表明了典型的UML到RDB的映射的一部分,因为它只处理二元关联。一个完整的映射将考虑n元关联,以及类之间的概括关联。
步骤Step 4:把类映射到表
你的下一步是在关联计划中定义UML类如何联系到表,如列表5所示。映射与前面的关联很相似;你将找到一个与每一个类同名的相关表。这里关键词when
保证了类和表的名字的同一性。关联的执行将把转换传播到每个类的属性,并把它们映射到相关列。这一关联还处理一对一和一对多关联。
|
由于这个例子只处理可浏览的关联,它假设关联的结束永远属于类。因此,所有实现关联结束的属性在关联计划中将同一个外键相关。多对多关联需要一个特殊映射,它在另一个关联中被单独处理。
关联TablePrimaryKeyColumn
应用于通讯,它向每一个同UML类相关的表添加一个主键.主键定义了一列或一组列,它们是表的唯一标识。它是SimpleRDB的通用模型的第一个类实体,但是在UML 2.0中没有它的对等体.这就是关联的签名没有与UML元素相关的形式参数的原因。它还解释了我们在通讯中使用特定多重性的原因。
此前RDL中的多重性概念还没有被介绍到;它使得通过指定上下限限制映射的数量——从一个关联中进行的例化——成为可能。在这个特定例子中,与一个关联且只与一个关联对应的多重性被定义为与关联TablePrimaryKeyColumn
对应(你可以看到列表5中的通讯介绍的括号符号)。如果没有指明,默认的多重性是从0到n,这表示映射尽可能多,在这里这是无关的,因为主键意味着唯一。
关联UmlAttribute2Column
把一个类的简单类型属性映射到表的一列。在这里使用匹配关键词是因为不是所有列都与UML属性相关;正如前文介绍过的,添加主键和外键是为了同时映射UML关联。在关联ClassOwnedEnd2ForeignKeyColumn
的例子中,我们把关联的结束——表示为类型的属性——映射到包含一个列和一个外键限制的外键。
图4 UmlClass2Table关联及其通讯
这个例子中贯穿的类图是RDL语法之外的概念的又一种可能表示。在这些类图中,关联被定义为带有特殊标记的类,它们的属性代表了输入中指明的形式参数。关联涉及多个模型类,它带有表示用于参数的值的属性。定义在关联的属性上的限制表明了限制候选值的作用域的条件。正如你在图4中看到的,property
和column
的角色是关联UmlAttribute2Column
的形式参数,而ownedAttributes
和columns
表示的是通讯涉及的实际值。
通讯是由两个关联之间的关联表明的,并带有详细的类型标记。到目前为止这个例子集中于由包含关联(属性包含于一个类,表包含于一个计划,等等)联系起来的类之间的通讯,并已经在类图中使用了累计符号来表示它们。尽管如此,在下一步骤中你将看到,有些通讯还可以用于由简单关联联系起来的类。在这种情况下,表示将是不同的,不带有累计符号。
步骤5:向表添加主键
列表6定义了一个列与一个主键之间的关联,后面的图5也表示了这点。该关联保证:
列表6 添加主键
|
指明列类型的内嵌通讯表现了RDL的声明性。在你的转换中,关联计划是目标模型,调和将为它产生值。对目标模型应用通讯是一种声明性规约,目标值就属此类。在我们的具体例子中,这意味着作为主键的每一列的类型都是INTEGER(整数)。这是本例子第一次把关联作为一个定义表达式(换句话说,一个赋值语句)的声明性陈述来使用,而不是用它作模型到模型的映射。
图5 TablePrimaryKeyColumn及其通讯
步骤6:把一个属性映射到一个列
列表7中的关联把一个UML属性映射到一个同名的列。在UML 2.0通用模型中,属性和可浏览关联结尾都被表达为类的属性。
|
关键词when
有多种应用并可以用于不同上下文。尽管所有这些使用都表达了一种条件,它们各自的含义是有区别的。定义于一个参数声明之内(比如,上面关联中的property
参数),when
从句定义了过滤用于通讯的候选值的方式。它只与参数域的属性有关,当它用在关联的签名结尾时,基于相关参数之间的特定关联,同一个从句定义了不同候选值组之间的共同标准(比如,属性和列的名称)。这称为一个识别条件。
在上面的关联中,你只想映射简单类型属性,因此你要确定应用于属性参数的过滤条件InstanceOf
限制,它保证了属性的类型是Datatype
。这一用户限制扩展了RDL语言,是MTF附带的util
扩展的一部分。
在关联体中,你用一个通讯来指明每个与匹配的属性相关的列的类型。为了简单起见,考虑把VARCHAR作为除主键和外键外的所有列的默认类型。
图6 UmlAttribute2Column关联
步骤7:把关联结尾映射到外键
列表8中的关联定义了一个关联结尾和一个外键之间的映射。根据前文介绍的原则,Rumbaugh et al. 认为按照它们的多重性,不同的关联映射用例:
列表8 映射一个关联结尾
|
这里你将集中于一对一和一对多的多重性,你将把它们作为单一的用例处理。多对多关联(由另一个关联处理)不在本文讨论范围之内。具体到本例,考虑所有关联结尾的外键的建立都带有多重性“1”。这样做的结果,将会出现下面两种情况:
你将注意到UML通用模型的结构暗示了关联映射的不同策略,无论它们的结尾是可浏览的(属于一个相关类)还是不可浏览的(属于关联本身)。为了访问关联结尾,你需要访问模型拥有的类,或者访问关联,或者两者都访问(如果可浏览的和不可浏览的结尾都要处理)。
为了简单起见,这个例子只支持两路可浏览关联。这使你能够通过访问类很容易地访问关联结尾,然后把相关属性映射到外键。
列表8表现的关联的一个特殊方面是使用了另一个关联(比如,CheckOne
)作为过滤值的条件。嵌套于域声明中,通讯可用于建立基于关联多重性的强大的过滤器。这里,它允许你定义更多表达能力很强的符号,而不止是严格的相等(比如,检查一个组中某个元素的存在性)。
这里使用关联CheckOne
是为了保证在关联执行时多对多关联不会被考虑进去。其他预声明的定义是为了保证作为参数提供的属性是一个关联结尾(还是以InstanceOf
为限制)。它们还检查了属性和列的名称的一致性是否被满足了。
当我们能够清楚无二义地联系任何关联结尾和外键时,我们可以把通讯应用于指明外键指向的是哪个主键。在SimpleRDB中,主键和外键被定义为一个和指向该列的键值限制相关联的列,以及一个同时拥有列和限制的表。此外,外键拥有一个refTable
属性,它指向另一个表定义的主键。在该通讯中,你需要表达主键和外键之间的联系,并指明该属性的值。
应用于属性property.type
和fkey.refTable
,UMLClassToTable
关联可被用来指明主键和外键之间的联系。在前面关联曾被用在通讯中来把模型拥有的每个类映射到一个关联计划。在两种情况下,目标都是把UML类(uml:Class
的实例)映射到关联表(rdb:Table
的实例)。
你在这里所作的有一点不同,主要是你把简单引用映射到已经相关的元素了(换句话说,映射到UML类)。这不同于映射累计关联,那样实际是建立新的元素。从映射的角度来看,这意味着你需要引用已有的映射(UmlClassToTable
的实例并联系一个已有类和表)而不是例化新的映射。
这就是本例在通讯中使用关键词ref
来表达对引用元素映射应该已经存在而无须再建立一个映射的含义。
图7 ClassOwnedEndToForeignKeyColumn关联及其通讯
列表9定义的关联是MTF中关联多种使用的又一个例子。这里你的主要兴趣是从关联建立相关的映射,然后对它们计数作为条件的一部分。从这个关联中例化的映射在调和时将不被考虑。
列表9 从一个关联建立映射
|
此关联的目的是过滤掉多对多关联,它由另一个关联处理。你在寻找多重性为1的关联结尾,记为1in UML2 (请见下面的图8)。
图8 CheckOne关联
本文中表现的关联实现了基于简单假设的从UML类到表的转换——以及UML一对一和一对多关联到键值限制的转换。它使你能够理解RDL语言的主要概念,即使此刻一个UML到关联的映射还未完全说明。多对多关联的情况这里没有讨论,因为它的实现与我们前面介绍的大同小异。整体原则是为每个相关关联建立第三个表,然后定义相关键值限制。这里没有详述的额外关联将作为例子的源代码的一部分出现。
更深入的概念,如抽象关联,或者抽象继承,也是RDL语言的一部分,但是本文将不作介绍。关于这些主题的更多资料可在MTF程序员指南中获得——请参见资源。
运行!
一旦关联定义完毕,你就可以在实际模型上执行转换了。本文使用了Primer Purchase Order模型,一个EMF书中(参见资源)描述的经典例子,如图9所示。UML2指南(参见资源)使用了一个变化版本,叫做Extended Purchase Order。我们使用UML2定义Primer Purchase Order模型,指明PurchaseOrder
,USAddress
和Item
类之间的双向关联,它在本文中可以获得。你将需要复制你工作区中的文件,最好是你为这个例子创建的项目。使用这个模型作为输入,你将能够运行一个MTF任务并产生相关的关联模型。
图9 改写Adapted Primer Purchase Order模型
MTF提供了一个高级发布界面,它允许你从Eclipse工作环境中触发转换。你只需要创建一个新的运行配置并输入需要的参数:
Uml2SimpleRdb
关联中使用ws
扩展的又一个原因,因为这样你在实例化IFile
类的时候可以用一个字符串指明文件。
MTF任务的配置画面如图10所示。你需要输入simplerdb.rdl
文件的路径,并把Uml2SimpleRdb
定义为输入文件。然后你需要指明源和目的变量的值;源变量是Primer Purchase Order文件的路径(将是不可修改的),而目标变量则指向要创建的关联计划的路径(必须被修改)。这个组合,包括可修改的参数,使你能够定义转换的方向。如果这一方面配置不当,调和将不会产生相关的结果。
图10 作为一个任务运行的Uml2SimpleRdb转换
规则应被顺利执行,一个对应于关联计划的新文件应在你的工作环境中出现。
MTF在Eclipse工作环境中的作用是提供映射视图,通过它你可以检查由转换引擎产生的映射。这个特性确实帮助你跟踪和理解转换的执行。当任务完成后,你可以点击出现在属性列底部的按钮。你将会看到和图11相似的结果。
图11 检查MTF产生的映射
结论
MTF为模型转换提供了一种声明的方式。在这篇文章中,我们简要描述了MTF语言并示范了它在从一个UML模型到关联计划的映射转换中的使用。MTF为自动模型转换和同步活动提供了各种可能性: