下图是Together 2006提供的各种不同的模型和MetaModel之间转换的能力。
看看上图中的rdb MetaModel,这就非常的有意思了。目前OR-Mapping技术似乎非常的流行,由Hibernate带起的风潮从Java平台一直延烧到.NET平台,虽然MS的ObjectSpace延迟了,但是并不代表MS会错过这股热潮,而Hibernate也移植到了.NET平台。不过我并不是要讨论Hibernate,而是要说说OR-Mapping。
Together 2006允许开发人员从UML模型转换到RDB模型,这代表由开发人员设计的类别图可以借由OCL的对映规则转换为关连资料库的纲要。Borland的ECO也提供了这个强大的能力,但是ECO目前并不允许开发人员自行定义转换的规则,而是由ECO架框本身来完成这个工作,但是Together 2006却提供了开放的架构,开发人员可以借由撰写OCL来本行定义这个OR-Mapping的流程,想想这是多么强大的功能。如果我们对映到Hibernate的话,那么我们也可以定义一个Java类别到rdb的转换规则,如此一来我们可以借由撰写OCL程式来客制化和自动化转换的流程。
看看下面一个Together 2006内建的范例,这个范例清楚的使用了OCL来转换UML模型到关连资料库纲要。
001 /*
002 * The SimpleUML to RDB Sample demonstrates how to use QVT transformations for
003 * transforming platform independent model to platform specific model.
004 *
005 * It also demonstrates the following basic features of QVT language:
006 * helper queries, mapping guards, and resolution operations.
007 *
008 * Sample model pim.simpleuml is included to be used as an input for the transformation.
009 */
010
011 transformation Simpleuml_To_Rdb;
012
013 metamodel 'http:///SimpleUML.ecore';
014 metamodel 'http:///rdb.ecore';
015
016 mapping main(in model: simpleuml::Model) : rdb::Model {
017 name := model.name;
018 schemas := package2schemas(model);
019 }
020
021 query package2schemas(in root: simpleuml::Package) : OrderedSet(rdb::Schema) {
022 package2schema(root)->
023 union(root.getSubpackages()->collect(p | package2schemas(p)))->asOrderedSet()
024 }
025
026 mapping package2schema(in pack: simpleuml::Package) : rdb::Schema
027 when { pack.hasPersistentClasses() }
028 {
029 name := pack.name;
030 elements := pack.ownedElements->select(oclIsKindOf(simpleuml::Class))->
031 collect(c | persistentClass2table(c.oclAsType(simpleuml::Class)))->asOrderedSet()
032 }
033
034 mapping persistentClass2table(in cls: simpleuml::Class) : rdb::Table
035 when { cls.isPersistent() }
036 {
037 name := cls.name;
038 columns := class2columns(cls);
039 primaryKey := class2primaryKey(cls);
040 foreignKeys := class2foreignKeys(cls);
041 }
042
043 mapping class2primaryKey(in cls: simpleuml::Class) : rdb::constraints::PrimaryKey {
044 name := 'PK'.concat(cls.name);
045 includedColumns := cls.resolveByRule('persistentClass2table', rdb::Table)->any(true).getPrimaryKeyColumns()
046 }
047
048 query class2foreignKeys(in cls: simpleuml::Class) : OrderedSet(rdb::constraints::ForeignKey) {
049 cls.attributes->collect(resolveByRule('relationshipAttribute2foreignKey', rdb::constraints::ForeignKey))->
050 asOrderedSet()
051 }
052
053 query class2columns(in cls: simpleuml::Class) : OrderedSet(rdb::TableColumn) {
054 dataType2columns(cls)->
055 union(generalizations2columns(cls))->asOrderedSet()
056 }
057
058 query dataType2columns(in dt: simpleuml::DataType) : OrderedSet(rdb::TableColumn) {
059 primitiveAttributes2columns(dt)->
060 union(enumerationAttributes2columns(dt))->
061 union(relationshipAttributes2columns(dt))->
062 union(assosiationAttributes2columns(dt))->asOrderedSet()
063 }
064
065 query dataType2primaryKeyColumns(in dt: simpleuml::DataType, in prefix : String, in leaveIsPrimaryKey : Boolean) : OrderedSet(rdb::TableColumn) {
066 dataType2columns(dt)->select(isPrimaryKey)->
067 collect(c | object rdb::TableColumn {
068 name := prefix.concat('_').concat(c.name);
069 domain := c.domain;
070 type := c.type;
071 isPrimaryKey := leaveIsPrimaryKey
072 })->asOrderedSet()
073 }
074
075 query primitiveAttributes2columns(in dt: simpleuml::DataType) : OrderedSet(rdb::TableColumn) {
076 dt.attributes->collect(a | primitiveAttribute2column(a))->asOrderedSet()
077 }
078
079 query umlPrimitive2rdbPrimitive(in name : String) : String {
080 if name = 'String' then 'varchar' else
081 if name = 'Boolean' then 'int' else
082 if name = 'Integer' then 'int' else
083 name
084 endif
085 endif
086 endif
087 }
088
089 mapping primitiveAttribute2column(in prop: simpleuml::Property) : rdb::TableColumn
090 when { prop.isPrimitive() }
091 {
092 isPrimaryKey := prop.isPrimaryKey();
093 name := prop.name;
094 type := object rdb::datatypes::PrimitiveDataType { name := umlPrimitive2rdbPrimitive(prop.type.name); };
095 }
096
097 query enumerationAttributes2columns(in dt: simpleuml::DataType) : OrderedSet(rdb::TableColumn) {
098 dt.attributes->collect(a | enumerationAttribute2column(a))->asOrderedSet()
099 }
100
101 mapping enumerationAttribute2column(in prop: simpleuml::Property) : rdb::TableColumn
102 when { prop.isEnumeration() }
103 {
104 isPrimaryKey := prop.isPrimaryKey();
105 name := prop.name;
106 type := object rdb::datatypes::PrimitiveDataType { name := 'int'; };
107 }
108
109 query relationshipAttributes2columns(in dt: simpleuml::DataType) : OrderedSet(rdb::TableColumn) {
110 dt.attributes->collect(a | relationshipAttribute2foreignKey(a))->
111 collect(includedColumns)->asOrderedSet();
112 }
113
114 mapping relationshipAttribute2foreignKey(in prop: simpleuml::Property) : rdb::constraints::ForeignKey
115 when { prop.isRelationship() }
116 {
117 name := 'FK'.concat(prop.name);
118 includedColumns := dataType2primaryKeyColumns(prop.type.asDataType(), prop.name, prop.isIdentifying());
119 referredUC := prop.type.lateResolveByRule('class2primaryKey', rdb::constraints::PrimaryKey);
120 }
121
122 query assosiationAttributes2columns(in dt: simpleuml::DataType) : OrderedSet(rdb::TableColumn) {
123 dt.attributes->select(isAssosiation())->
124 collect(p | dataType2columns(p.type.asDataType()))->asOrderedSet()
125 }
126
127 query generalizations2columns(in cls: simpleuml::Class) : OrderedSet(rdb::TableColumn) {
128 cls.generalizations->collect(g | class2columns(g.general))->asOrderedSet()
129 }
130
131 query simpleuml::Package::getSubpackages() : OrderedSet(simpleuml::Package) {
132 ownedElements->collect(oclAsType(simpleuml::Package))->asOrderedSet()
133 }
134
135 query simpleuml::Type::asDataType() : simpleuml::DataType {
136 oclAsType(simpleuml::DataType)
137 }
138
139 query simpleuml::Property::isPrimaryKey() : Boolean {
140 stereotype->includes('primaryKey')
141 }
142
143 query simpleuml::Property::isIdentifying() : Boolean {
144 stereotype->includes('identifying')
145 }
146
147 query simpleuml::Property::isPrimitive() : Boolean {
148 type.oclIsKindOf(simpleuml::PrimitiveType)
149 }
150
151 query simpleuml::Property::isEnumeration() : Boolean {
152 type.oclIsKindOf(simpleuml::Enumeration)
153 }
154
155 query simpleuml::Property::isRelationship() : Boolean {
156 type.oclIsKindOf(simpleuml::DataType) and type.isPersistent()
157 }
158
159 query simpleuml::Property::isAssosiation() : Boolean {
160 type.oclIsKindOf(simpleuml::DataType) and not type.isPersistent()
161 }
162
163 query rdb::Table::getPrimaryKeyColumns() : OrderedSet(rdb::TableColumn) {
164 columns->select(isPrimaryKey)
165 }
166
167 query simpleuml::ModelElement::isPersistent() : Boolean {
168 stereotype->includes('persistent')
169 }
170
171 query simpleuml::Package::hasPersistentClasses() : Boolean {
172 -- ownedElements->exists(e | e.oclIsKindOf(simpleuml::Class)
173 -- and e.oclAsType(simpleuml::Class).isPersistent())
174 ownedElements->select(oclIsKindOf(simpleuml::Class))->
175 select(c | c.oclAsType(simpleuml::Class).isPersistent())->size() > 0
176 }
看看它的主要进入点,接受一个UML模型,转换出一个rdb模型,整个OCL程式并不困难了解,其中一些查询函式更是有趣,例如决定primarykey栏位使用了 :
139 query simpleuml::Property::isPrimaryKey() : Boolean {
140 stereotype->includes('primaryKey')
141 }
它是根据UML模型中是否有定义'primaryKey'的stereotype来决定,而在决定把UML类别图转换为关连资料库纲要的过程中,我们只需要处理Persistent型态的类别即可,这则是由下面的OCL函式来进行:
171 query simpleuml::Package::hasPersistentClasses() : Boolean {
172 -- ownedElements->exists(e | e.oclIsKindOf(simpleuml::Class)
173 -- and e.oclAsType(simpleuml::Class).isPersistent())
174 ownedElements->select(oclIsKindOf(simpleuml::Class))->
175 select(c | c.oclAsType(simpleuml::Class).isPersistent())->size() > 0
176 }
它如何找到模型中需要永续储存的类别? 简单,首先从所有拥有的模型元素中选择出是类别的元素:
ownedElements->select(oclIsKindOf(simpleuml::Class))->
再对其中每一个类别元素,看看它是否有使用stereotype来定义'persistent'属性:
select(c | c.oclAsType(simpleuml::Class).isPersistent())
167 query simpleuml::ModelElement::isPersistent() : Boolean {
168 stereotype->includes('persistent')
169 }
最后再检查所有select出来的collection物件是否有符合条件的类别元素即可得到答案:
->size() > 0
是不是觉得OCL又有趣又符合物件导向和直觉呢?
撰写OCL成为了一个很有趣的工作,因为它会强迫开发人员以物件导向的方式思考,又强迫开发人员以模型和MetaModel做为物件的处理来源,一旦您习惯使用OCL之后,您会发现它和我们使用的物件导向程式语言,物件导向分析,物件导向设计以及模型是如此的搭配,它会让您觉得撰写OCL是很享受的一件事。
有了OCL能够让我们定义模型之间转换的规则什么好处? 好处多了,例如一个企业如果旧的系统都是使用流程图(Flow Chart)或是资料图(Data Diagram)等定义的,那么就可以借由撰写OCL对映规则让Together 2006执行它并且自动的转换流程图/资料图为UML模型。又例如一个企业已经拥有了许多的程式码,函式库或是架框,又不希望传统的UML工具只能根据模型产生简单的类别程式码,而希望能够让模型直接对映到企业已经发展出来的程式码,函式库或是架框,进行UML PIM到特定程式码,函式库或是架框PSM的对映,那么现在借由Together 2006和OCL现在这都可以做到了。如此一来企业可以大幅减少根据模型再转换到实作程式码之间的时间和成本,更重要的是一旦OCL对映程式码开发完成之后,可以不断的自动化模型到程式码,函式库或是架框之间对映的工作。
如此看来不管是在Java平台,.NET平台,Win32平台或是UML的软体工程世界中,OCL都成为了不可或缺的重要的语言。也因此,对于开发人员来说Java+SQL+OCL,或是C#+SQL+OCL,或是Delphi+SQL+OCL似乎是Java,C#和Delphi开发人员必须熟练的金三角语言了。不过现在再加上OCL的语法,我脑袋中各种语言语法就更混乱了,希望我下次做产品发表或是技术研讨会时我还能够无误的打出正确的语法!
最后在年初开春时先祝各位2006年IT功力大增, 事业更上一层楼。