下圖是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功力大增, 事業更上一層樓。