2006 年 01 月 的封存

送別2005,迎接2006!

在我的朋友和我說新年快樂時,我才猛然想到2005就要過去了。回想2005年開始的時候,我真的覺得2005年將很難過,因為工作充滿了挑戰,IT技術更是令人眼花撩亂,讓我們這些技術人員有時會被客戶問得下不了台,不多瞭解一些重要的技術我這個飯碗是很難拿得穩的,卻沒想到轉眼間2005就這麼過去了。

 

2005年未之際,許多人都會預測2006會發生什麼事情,星象學家都愛預測2006年會發生什麼世界大事,那一個演藝明星又會怎麼樣。我比較關心的是2006年的經濟發展,因此對於一些我重視的經濟學家,財務分析師的預測就特別注意,蓋因這我和我投資的養老基金有極大的關係之故。

 

不過我畢竟是IT人,因此也對於許多IT界名人對於2006IT技術趨勢的預測也非常的興趣。我不知道各位的習慣,但是我個人的習慣是我不只喜歡看這些預測,自己也會提出自己的預測,更重要的是我在看這些預測時,對於每一條預測我會試著去分析它,試著瞭解這些IT界名人為什麼會提出這些預測,他們的預測有什麼道理或是理論支持? 我贊成這些預測或是推論嗎? 當我開等待車塞車時,這是消磨時間很好的題材。我想日後如果有機會有IT分析師這種工作,我也蠻適合的

 

為了讓各位分享一下我最近看到的兩個預測,我在下面的表格中整理了2IT名人對於2006IT趨勢的預測:

Marco Cantu

Ted Neward

Borland can make the right decisions (thanks to the new CEO) and help Delphi prosper

The hype surrounding Ajax will slowly fade, as people come to realize that there’s really nothing new here, just that DHTML is cool again. As Dion points out, Ajax will become a toolbox that you use in web development without thinking that "I am doing Ajax". Just as we don’t think about "doing HTML" vs "doing DOM".

Borland can make the right decisions (thanks to the new CEO) and help Delphi prosper

The release of EJB 3 may actually start people thinking about EJB again, but hopefully this time in a more pragmatic and less hype-driven fashion. (Yes, EJB does have its place in the world, folks–it’s just a much smaller place than most of the EJB vendors and book authors wanted it to be.)

Google keeps delivering valuable tools for free (is their Office suite coming?)

Vista will be slipped to 2007, despite Microsoft’s best efforts. In the meantime, however, WinFX (which is effectively .NET 3.0) will ship, and people will discover that Workflow (WWF) is by far the more interesting of the WPF/WCF/WWF triplet. Notice that I don’t say "powerful" or "important", but "interesting".

Skype and similar communication tools shorted the distance among people and let me/us all telecommute a little more (and put some nonsense telecoms out of business)

Scripting languages will hit their peak interest period in 2006; Ruby conversions will be at its apogee, and its likely that somewhere in the latter half of 2006 we’ll hear about the first major Ruby project failure, most likely from a large consulting firm that tries to duplicate the success of Ruby’s evangelists (Dave Thomas, David Geary, and the other Rubyists I know of from the NFJS tour) by throwing Ruby at a project without really understanding it. In other words, same story, different technology, same result. By 2007 the Ruby Backlash will have begun.

Microsoft figures out how to server their customers real needs (rather than Bill Gates’ ones) delivering good OSes and apps for their real price

Interest in building languages that somehow bridge the gap between static and dynamic languages will start to grow, most likely beginning with E4X, the variant of ECMAScript (Javascript to those of you unfamiliar with the standards) that integrates XML into the language.

Linux makes further inroads on servers and desktops alike

Java developers will start gaining interest in building rich Java apps again. (Freely admit, this is a long shot, but the work being done by the Swing researchers at Sun, not least of which is Romain Guy, will by the middle of 2006 probably be ready for prime-time consumption, and there’s some seriously interesting sh*t in there.)

database vendors stop locking people into their platforms

Somebody at Microsoft starts seriously hammering on the CLR team to support continuations. Talk emerges about supporting it in the 4.0 (post-WinFX) release.

XML and Web Services fuel a new IT renaissance (so that we’ll get back to having more work and batter paid jobs)

Effective Java (2nd Edition) will ship. (Hardly a difficult prediction to make–Josh said as much in the Javapolis interview I did with him and Neal Gafter.)

web development with AJAX and other technologies helps us deliver better apps in a shorted time, having some fun alongside

Effective .NET will ship.

open source foundations (mozilla with firefox, open office, firebird and many more) prosper

Pragmatic XML Services will ship

and, finally, someone figures out an antispam mechanism that works making the internet a nice place again, he’ll deserver to get rich for sure!

JDK 6 will ship, and a good chunk of the Java community self-proclaimed experts and cognoscente will claim it sucks

 

Java developers will seriously begin to talk about what changes we want/need to Java for JDK 7 ("Dolphin"). Lots of ideas will be put forth. Hopefully most will be shot down. With any luck, Joshua Bloch and Neal Gafter will still be involved in the process, and will keep tight rein on the more… aggressive… ideas and turn them into useful things that won’t break the spirit of the platform.

 

My long-shot hope, rather than prediction, for 2006: Sun comes to realize that the Java platform isn’t about the language, but the platform, and begin to give serious credence and hope behind a multi-linguistic JVM ecosystem

 

My long-shot dream: JBoss goes out of business, the JBoss source code goes back to being maintained by developers whose principal interest is in maintaining open-source projects rather than making money, and it all gets folded together with what the Geronimo folks are doing. In other words, the open-source community stops the infighting and starts pulling oars in the same direction at the same time. For once.

 

不知道各位看完上述的預測之後有什麼想法? 您有注意什麼異樣的地方嗎? 我常常在想,我們每一個人目前最重要的資產就是:健康和頭腦,而明日的你最重要的資產就是:眼光。我們每天不管多忙,都應該花些時間經營自己重要的資產,讓自己更有數值,不是嗎?

 

在即將送走2005之際,不如在今天送給自己一個紅包,在紅包中寫下您自己對於2006年的10大預測,並且和自己約定一個再度開啟這個紅包的日期,也許是2006年底。看看您今日的預測到時會有多少的準確度。當然,您應該從您熟悉的技術或是環境進行10大預測,而不是天馬行空。為什麼要這做這10大預測? 很簡單,為了鍛鍊您最寶貴的資產之一:頭腦,並且投資明日的您自己:眼光。通常人們在進行預測時,不是因為他身處的專業讓他有感覺,不然就代表是人們心底深處渴望發生的事情。在現在許自己一些預測,也有助於我們戮力以赴的完成它。例如我可以給自己一個預測,預測2006年我可以認真的唸完10IT專業的好書,呵呵,那麼為了讓我的預測成真,我就得好好的研究一下2006年我希望唸些那方面的書籍,整理出書籍清單,並且認真的在2006年一一的實現,這不是很好嗎?

 

講到這裡我才驚訝的發現2005年我並沒有唸太多的書,忙碌了一年竟然很少花時間讓自己沈澱下來吸收知識,實在是很失敗的一件事,因此在2005年最後幾天一定要好好的閱讀一些自己鎖定的目標。

 

好,現在回來讓我們看看您對於預測的判斷是什麼。在上面2個不同的2006年預測中,您觀察到了什麼事情? 我不知道您觀察到了什麼,但我到是很快的觀察到下面2件事情:

n          Ted Neward一定是Java的背景

n          2位仁兄對Ajax的預測看法顯然不同

 

現在讓我們先鎖定Ajax這個話題,在繼續往下看本文之前問一下您自己,您對於Ajax的看法是什麼? 您贊成Marco的看法呢? 還是Ted的看法? 重要的是為什麼?

 

想想iPod為什麼會成功? MP3iPod創造出來的嗎? 當然不是,iPod的成功是因為它成功的結合了設計,音樂,機動性,而創造了新的使用者經驗(User Experience)Ajax也不是以創新的技術為本,而同樣是提供了以往未曾有的使用者經驗。因此Ajax會不再流行嗎? No ,no,我覺得Ajax會繼續的進化,Ajax會結合Web ServiceSOA,再次提供更新的使用者經驗。為什麼? 因為Ajax4大支柱之一就是從後端擷取用戶端必要的資料或是服務,那種技術正是為了這種目的提出的呢? 不正是Web ServiceSOA?Ajax + Web Service + SOA也許是2006年的重點之一。

 

再看看最近幾年流行的技術,Web ServiceSOA,不都是使用舊的,或是已經存在的技術結合新的應用而出現的嗎? 其實看到Ajax讓我又想起了好幾年前我朋友使用Applet撰寫的專案,他也是幾乎把整個系統使用Applet寫完之後一次下載到用戶端執行。當然,最後他的系統失敗了,因為當時的瀏覽器,頻寬,機器,技術都沒有成熟。但是現在Ajax面對卻完全不一樣,瀏覽器,頻寬,機器,技術都已經成熟而且強大。此外當伺服端,主從架構,中介軟體都輪流做主一段時間之後,人們又想起了讓強大的用戶端只執行少少的工作,是不太正常的 。而且使用者最常面對的就是用戶端,因此讓用戶端分享計算負擔並且提供新的使用者經驗將是未來幾年最重要的技術走向的趨勢。

 

我再回頭去看看MSAjax解決方案:AtlasAtlas提供了許多的功能,我不會在這裡討論,如果您有興趣的話,您可以在下面的URL找到它:

http://www.asp.net/default.aspx?tabindex=9&tabid=47

 

我提Atlas的目的不是因為那些許多的功能,而是我看到了我喜歡的一點,更安全,更物件導向的JavaScript。我個人認為這很重要,因為如果一個開發人員真的對Ajax很認真的話,那麼就需要瞭解下載到用戶端的程式碼可能會很大,很複雜,那麼一個更安全,更物件導向的JavaScript可以幫助我們減少很多的問題。

 

好了,現在您準備好給自己的紅包了嗎?

 

上星期博文視點的人(sheguang)告訴我,CSDN、《程式師》雜誌社、中華讀書報、第二書店、Donews共同舉辦的"2005年十大電腦好書"評選活動公佈了獲獎名單。我在2005年用極少時間勉強擠出來完成的兩本書籍:物件導向開發實踐之路-Delphi版以及物件導向開發實踐之路-C#版都入選了,分別名列第7名和第12名。聽到這個消息著實讓我驚訝,一是因為Delphi已經是一個歷史相當久的產品,沒有想到Delphi的書籍還能和最新的Java/.NET書籍競爭。二是因為我是第一次寫C#的書籍,在C#出版書籍作者中算是一個蔡鳥,而且在C#的開發圈中有著太多的競爭書籍,因此這本書也能夠在去年出版只有3,4個月的時間就讓C#的朋友接受也令我意外。回想一下,能夠連續在200320042005年都有我的著作入選大中華區好書榜,我想這已經是對於一個業餘作者最大的鼓勵了,我應該謝謝所有支持我的讀者,這也是我在結束2005年之際所收到最好的一個禮物。

 

 

6 則迴響

中文Borland Developer Network和技術文章

中文BDN是我目前工作的要點之一,雖然我仍然在緊鑼密鼓的實作中,但是BDN上許多精彩的技術文章卻已經一一的出現了,噢,我是指中文化的技術文章。在未來當我把中文BDN完成之後,這些中文化的技術文章可望像目前美國的BDN一樣出現在一個集中的地方。現在呢雖然我已經提供了好幾篇中文化的技術文章,但是似乎沒有什麼人知道,因為它目前隱藏在比較少人去的網頁中,如果您想閱讀由Borland提供的精彩BDN技術文章,您現在可以先在下面的URL中找到:

http://www.borland.com/cn/products/delphi/index.html

 

希望未來我們大中華區也能夠有人寫寫技術BDN文章! 

Cheers!

9 則迴響

為支援Unicode準備-VCL和dbExpress

BDS 2006的功能文件中著明了dbExpressMS SQL Server驅動程式支援MS SQL 2005以及Unicode,這非常的有趣。為什麼? 因為這牽涉到了許多的事情。首先許多Delphi/C++Builder的使用者早已要求Borland推出支援UnicodeVCL架框,這個支援UnicodeVCL架框早在Delphi 2時我便向Delphi R&D團隊要求,因為當時我在台灣負責和新加坡同事為Delphi開發中文套件就我就覺得有這個需要。而當時Anders也認為這很重要,也準備為VCL架框加入Unicode的能力,可惜的是人去樓空,隨著Anders的離開這個功能也就一直被懸而未決。

 

隨著多語言環境不斷的成長和成熟,UnicodeVCL架框似乎再也不能拖了,因此Borland2005DelphiRoadmap上宣示了在Delphi 64位元的版本中,VCL架框將支援Unicode。那麼對於目前需要UnicodeDelphi/C++Builder開發人員要怎麼辦呢? 就我所知,許多人目前都是使用TNT控制項來解決這個需求:

 

 

即然VCL架框在未來才準備支援Unicode,而BDS 2006MS SQL Server dbExpress驅動程式已經支援了Unicode,那麼這是怎麼做到的? 其實Borland雖然在未來才準備讓VCL架框支援Unicode,但是在BDS 2006VCL架框中卻已經開始了這個工作,修改了許多VCL的方法,特性,事件和類別,介面等。也加入了一些新的類別,如此一來才能讓dbExpress驅動程式和VCL架框配合。例如在BDS 2006VCL架框中,和dbExpress相關的TCustomSQLDataSet類別已經改成是從TWideDataSet繼承下來,而不再是像Delphi 2005和以前的版本,是直接從TDataSet繼承下來。

  TCustomSQLDataSet = class(TWideDataSet)

  Private

   

 

TWideDataset是一個新加入的類別,仔細看看TWideDataSet類別,主要是加入了WideString的支援宣告,為Unicode做準備,此外TWideDataset也複載了許多支援WideString的方法,為執行包含UnicodeSQL敘述和命令做準備。

{ TWideDataset }

 

  TWideDataSet = class(TDataSet, IProviderSupport2)

  protected

    function PSExecuteStatement(const ASQL: string; AParams: TParams;

      ResultSet: Pointer = nil): Integer; overload; override; deprecated;

    function PSExecuteStatement(const ASQL: WideString; AParams: TParams;

      ResultSet: Pointer = nil): Integer; overload; override;

    function PSGetCommandText: string; override; deprecated;

    function PSGetCommandTextW: WideString; override;

    function PSGetKeyFields: string; override; deprecated;

    function PSGetKeyFieldsW: WideString; override;

    function PSGetQuoteChar: string; override; deprecated;

    function PSGetQuoteCharW: WideString; override;

    function PSGetTableName: string; override; deprecated;

    function PSGetTableNameW: WideString; override;

    procedure PSSetCommandText(const CommandText: string); overload; override; deprecated;

    procedure PSSetCommandText(const CommandText: WideString); overload; override;

    function IProviderSupport2.PSGetCommandText = PSGetCommandTextW;

    function IProviderSupport2.PSGetKeyFields = PSGetKeyFieldsW;

    function IProviderSupport2.PSGetQuoteChar = PSGetQuoteCharW;

    function IProviderSupport2.PSGetTableName = PSGetTableNameW;

  end;

 

除此之外,對於dbExpress來說,BDS 2006也加入了一些新的wrapper類別來封裝dbExpress的介面,例如對於dbExpressISQLMetaData介面,Delphi 2005加入了TISQLMetaData25封裝類別,BDS 2006則加入了TISQLMetaData30封裝類別。TISQLMetaData25封裝類別複載了抽象虛擬類別 : TISQLMetaData抽象虛擬類別,並且使用做為實作的介面。

  TISQLMetaData25 = class(TISQLMetaData)

  protected

    I : ISQLMetaData25;

  public

    constructor Create(newMetaData: ISQLMetaData25);

    destructor Destroy; override;

    function SetOption(eDOption: TSQLMetaDataOption;

                     PropValue: LongInt): SQLResult; override;

    function SetStringOption(eDOption: TSQLMetaDataOption;

                     const str: WideString): SQLResult; override;

    function GetOption(eDOption: TSQLMetaDataOption; PropValue: Pointer;

                     MaxLength: SmallInt; out Length: SmallInt): SQLResult; override;

    function GetStringOption(eDOption: TSQLMetaDataOption;

                     var str: WideString): SQLResult; override;

    function getObjectList(eObjType: TSQLObjectType; var Cursor: TISQLCursor):

                     SQLResult; override;

    function getTables(TableName: PWideChar; TableType: LongWord;

                     var Cursor: TISQLCursor): SQLResult; override;

    function getProcedures(ProcedureName: PWideChar; ProcType: LongWord;

                     var Cursor: TISQLCursor): SQLResult; override;

    function getColumns(TableName: PWideChar; ColumnName: PWideChar;

                     ColType: LongWord; var Cursor: TISQLCursor): SQLResult; override;

    function getProcedureParams(ProcName: PWideChar; ParamName: PWideChar;

                     var Cursor: TISQLCursor): SQLResult; override;

    function getIndices(TableName: PWideChar; IndexType: LongWord;

                     var Cursor: TISQLCursor): SQLResult; override;

    function getErrorMessage(Error: PWideChar): SQLResult; overload; override;

    function getErrorMessageLen(out ErrorLen: SmallInt): SQLResult; override;

    function getErrorMessage(var Error: WideString): SQLResult; overload; override;

  end;

 

TISQLMetaData30也是複載了抽象虛擬類別 : TISQLMetaData抽象虛擬類別,但是使用了ISQLMetaData30介面做為實作的依據。

  TISQLMetaData30 = class(TISQLMetaData)

  protected

    I : ISQLMetaData30;

  public

    constructor Create(newMetaData: ISQLMetaData30);

    destructor Destroy; override;

    function SetOption(eDOption: TSQLMetaDataOption;

                     PropValue: LongInt): SQLResult; override;

    function SetStringOption(eDOption: TSQLMetaDataOption;

                     const str: WideString): SQLResult; override;

    function GetOption(eDOption: TSQLMetaDataOption; PropValue: Pointer;

                     MaxLength: SmallInt; out Length: SmallInt): SQLResult; override;

    function GetStringOption(eDOption: TSQLMetaDataOption;

                     var str: WideString): SQLResult; override;

    function getObjectList(eObjType: TSQLObjectType; var Cursor: TISQLCursor):

                     SQLResult; override;

    function getTables(TableName: PWideChar; TableType: LongWord;

                     var Cursor: TISQLCursor): SQLResult; override;

    function getProcedures(ProcedureName: PWideChar; ProcType: LongWord;

                     var Cursor: TISQLCursor): SQLResult; override;

    function getColumns(TableName: PWideChar; ColumnName: PWideChar;

                     ColType: LongWord; var Cursor: TISQLCursor): SQLResult; override;

    function getProcedureParams(ProcName: PWideChar; ParamName: PWideChar;

                     var Cursor: TISQLCursor): SQLResult; override;

    function getIndices(TableName: PWideChar; IndexType: LongWord;

                     var Cursor: TISQLCursor): SQLResult; override;

    function getErrorMessage(Error: PWideChar): SQLResult; overload; override;

    function getErrorMessageLen(out ErrorLen: SmallInt): SQLResult; override;

    function getErrorMessage(var Error: WideString): SQLResult; overload; override;

  end;

 

那麼ISQLMetaData25ISQLMetaData30有什麼不同? 細節看看下面這兩個介面的差異:

ISQLMetaData25 = interface(ISQLMetaData)

   function SetOption(eDOption: TSQLMetaDataOption;

                     PropValue: LongInt): SQLResult; stdcall;

   function GetOption(eDOption: TSQLMetaDataOption; PropValue: Pointer;

                     MaxLength: SmallInt; out Length: SmallInt): SQLResult; stdcall;

   function getObjectList(eObjType: TSQLObjectType; out Cursor: ISQLCursor25):

                     SQLResult; stdcall;

   function getTables(TableName: PChar; TableType: LongWord;

                     out Cursor: ISQLCursor25): SQLResult; stdcall;

   function getProcedures(ProcedureName: PChar; ProcType: LongWord;

                     out Cursor: ISQLCursor25): SQLResult; stdcall;

   function getColumns(TableName: PChar; ColumnName: PChar;

                     ColType: LongWord; Out Cursor: ISQLCursor25): SQLResult; stdcall;

   function getProcedureParams(ProcName: PChar; ParamName: PChar;

                     out Cursor: ISQLCursor25): SQLResult; stdcall;

   function getIndices(TableName: PChar; IndexType: LongWord;

                     out Cursor: ISQLCursor25): SQLResult; stdcall;

   function getErrorMessage(Error: PChar): SQLResult; overload; stdcall;

   function getErrorMessageLen(out ErrorLen: SmallInt): SQLResult; stdcall;

end;

 

我們可以清楚的看到ISQLMetaData25ISQLMetaData30介面不同的地方是ISQLMetaData30完全使用WideString的格式來處理後理後端的資料來源,而ISQLMetaData25則是使用一般的字串格式,這代表ISQLMetaData30介面能夠處理Unicode的資料。當然,BDS 2005其他封裝dbExpress介面的wrapper類別也是類似的。

ISQLMetaData30 = interface(ISQLMetaData)

   function SetOption(eDOption: TSQLMetaDataOption;

                     PropValue: LongInt): SQLResult; stdcall;

   function GetOption(eDOption: TSQLMetaDataOption; PropValue: Pointer;

                     MaxLength: SmallInt; out Length: SmallInt): SQLResult; stdcall;

   function getObjectList(eObjType: TSQLObjectType; out Cursor: ISQLCursor30):

                     SQLResult; stdcall;

   function getTables(TableName: PWideChar; TableType: LongWord;

                     out Cursor: ISQLCursor30): SQLResult; stdcall;

   function getProcedures(ProcedureName: PWideChar; ProcType: LongWord;

                     out Cursor: ISQLCursor30): SQLResult; stdcall;

   function getColumns(TableName: PWideChar; ColumnName: PWideChar;

                     ColType: LongWord; Out Cursor: ISQLCursor30): SQLResult; stdcall;

   function getProcedureParams(ProcName: PWideChar; ParamName: PWideChar;

                     out Cursor: ISQLCursor30): SQLResult; stdcall;

   function getIndices(TableName: PWideChar; IndexType: LongWord;

                     out Cursor: ISQLCursor30): SQLResult; stdcall;

   function getErrorMessage(Error: PWideChar): SQLResult; overload; stdcall;

   function getErrorMessageLen(out ErrorLen: SmallInt): SQLResult; stdcall;

end;

 

現在封裝dbExpressVCL類別都已經完成了對於Unicode的支援,就等Borland推出支援各種關連資料庫的dbExpress驅動程式即可。而VCL架框也在穩定的朝向支援Unicode的方向前進,依照DelphiRoadmap,我們將可望在Borland推出代號為HighLanderBDS之後,看到全新架構的VCL架框。

 

在此時和dbExpress 3.0玩玩真的很有意思, dbExpress 3.0不但功能更多了, 效率也比以前的dbExpress快上了許多, 可以說是目前Win32下最好的資料存取技術了.

10 則迴響

OCL的樂趣和威力 Part 2

下圖是Together 2006提供的各種不同的模型和MetaModel之間轉換的能力。

 

看看上圖中的rdb MetaModel,這就非常的有意思了。目前OR-Mapping技術似乎非常的流行,由Hibernate帶起的風潮從Java平台一直延燒到.NET平台,雖然MSObjectSpace延遲了,但是並不代表MS會錯過這股熱潮,而Hibernate也移植到了.NET平台。不過我並不是要討論Hibernate,而是要說說OR-Mapping

 

Together 2006允許開發人員從UML模型轉換到RDB模型,這代表由開發人員設計的類別圖可以藉由OCL的對映規則轉換為關連資料庫的綱要。BorlandECO也提供了這個強大的能力,但是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 2006OCL現在這都可以做到了。如此一來企業可以大幅減少根據模型再轉換到實作程式碼之間的時間和成本,更重要的是一旦OCL對映程式碼開發完成之後,可以不斷的自動化模型到程式碼,函式庫或是架框之間對映的工作。

 

如此看來不管是在Java平台,.NET平台,Win32平台或是UML的軟體工程世界中,OCL都成為了不可或缺的重要的語言。也因此,對於開發人員來說Java+SQL+OCL,或是C#+SQL+OCL,或是Delphi+SQL+OCL似乎是JavaC#Delphi開發人員必須熟練的金三角語言了。不過現在再加上OCL的語法,我腦袋中各種語言語法就更混亂了,希望我下次做產品發表或是技術研討會時我還能夠無誤的打出正確的語法!

 

最後在年初開春時先祝各位2006IT功力大增, 事業更上一層樓。

7 則迴響

OCL的樂趣和威力 Part 1

會讓我寫這篇Blog的原因實在是因為OCL太好玩了,而且OCL的威力也逐漸讓我有愈來愈深的體認,OCL現在已經成為我個人認為最重要的語言之一,我應該稱OCL是『程式語言』?還是『正規語言』?還是『模型轉換語言』?這我也不知道,這是因為OCL雖然是OMG的標準『正規語言』,大多數的人對於OCL的觀念還停留在OCL是使用於UML模型中定義條件,限制,或是使用於MDA之中。這兩者的應用都希望藉由OCL能夠更精確的定義模型的意義,以及藉由OCL撰寫和特定平台,語言,技術無關的企業邏輯,以便在實作或是應用MDA的模型轉換過程中能夠順利產生有意義的結果模式,例如從PIM轉換到PSM,從PSM轉換到特定的程式語言,例如JavaC#或是Delphi等。

 

不過OCL在一些軟體廠商,例如Borland,不斷的改善之下,在原本只是唯讀的OCL加入延伸的功能,以便讓OCL具備修改和寫入的能力。為什麼要這樣做? 請想一想,如果要在各種模型使用OCL做為和模型,平台,技術無關的中立語言,那麼OCL只有唯讀能力是不足的,例如在敘述圖中使用OCL做為定義狀態改變的標準語言,那麼當狀態改變時如何能夠使用唯讀的OCL來改變狀態? 那要改變使用特定的程式語言來敘述嗎?如果是這樣的話,為什麼還要使用OCL? 再想想,於模型轉換時,我們需要有來源模型以及轉換後的結果模型。結果模型是根據轉換規則而產生的,因此如果我們決定使用OCL來定義,對映模型轉換的規則,那麼OCL必須具備建立結果模型的能力,否則我們要使用什麼語言來敘述結果模型?

 

因此BorlandECO便根據OCL加以延伸形成Action Language,其中最重要的就是允許開發人員使用OCL進行修改,寫入或是建立物件的功能。

例如OCL藉由加入:=運算元而擁有了指定功能之後,在狀態圖中我們才能夠改變應用程式執行時的狀態:

self.FirstName := home.owners->first.FirstName

 

至於Together 2006就對OCL加入了更多的能力了,因為Together 2006選擇了使用OCL語言來定義模式和模式之間轉換的規則。嚴格的說,應該是指Together 2006藉由強化OCL語法讓開發人員能夠定義MetaModelMetaModel之間對映的關係,如此一來Together 2006就能夠藉由OCLMetaModel來轉換不同的模型了。 這可以由我在上海/成都時展示的範例來說明。

 

200512月時,我在上海/成都展示了如何把一個由BPMN敘述的企業流程轉換為UMLUseCaseBPMN模型通常是由商業人士使用來敘述企業領域的商業流程,當領域專家(Domain Expert)使用BPMN敘述了特定的企業流程之後,必須交由IT人員來開發系統。但是對於IT人員來說,BPMN模型可能很陌生,而且大多數的IT開發工具都只接受UML模型而不是更抽象的BPMN模型。因此我們需要把領域專家實作的商業模型轉換為IT人員熟悉的UML模型。

 

當時我使用的BPMN模型如下所示:

 

我希望把這個BPMN的模型轉換為UMLUseCase,如下所示:

 

這要如何做到? 其實這正是MetaModelMetaModel之間轉換的範例,要轉換這兩個特定的模型非常的簡單,我們只需要定義BPMNMetaModel之間的每一個元素如何對映到UMLMetaModel的每一個元素,那麼一旦這個對映規則定義好了之後,任何的BPMN就可以轉換為UML之間的模型了,下圖就說明了這個觀念。

 

 

001    transformation Bpmn_To_Uml;

002   

003    metamodel ‘http://www.borland.com/together/2005/bpmn’;

004    metamodel ‘http://www.borland.com/together/uml’;

005    metamodel ‘http://www.borland.com/together/uml20’;

006   

007    mapping main(in model: bpmn::BpmnProcessPool): uml::together::Model {

008        init {

009            var actors := model.lanes->select(lane | lane.oclIsKindOf(bpmn::BpmnLane))->oclAsType(Sequence(bpmn::BpmnLane));

010            var tasks := model.lanes.flowObjects->select(lane | lane.oclIsKindOf(bpmn::BpmnTask))->oclAsType(Sequence(bpmn::BpmnTask));

011        }

012        object {

013            ownedMembers := actors->collect(a | makeActor(a))->asOrderedSet();

014            ownedMembers += tasks->collect(t | makeUseCase(t))->asOrderedSet();

015        }

016    }

017   

018   

019    mapping makeActor(in lane: bpmn::BpmnLane): uml20::usecases::Actor {

020        object {

021            name := lane.name;

022            description := lane.documentation;

023           

024        }

025    }

026   

027    mapping makeUseCase(in task: bpmn::BpmnTask): uml20::usecases::UseCase {

028        object {

029            name := task.name;

030            description := task.documentation;

031        }

032    }

 

007行的mapping main定義了一個QVT轉換的主進入點,這是一個什麼樣的轉換呢?main signature就可以瞭解,它接受一個BPMN的流程模型,

(in model: bpmn::BpmnProcessPool)

轉換並且回傳一個Together的模型

uml::together::Model

 

接著我們定義了下面兩個對映MetaModelMetaModel之間元素的規則:

n          每一個BPMN MetaModel之中的Lane元素都對映到UMLUseCase圖形中的Actor元素

n          每一個BPMN MetaModel之中的Task元素都對映到UMLUseCase圖形中的UseCase元素

 

有了這兩個規則之後,一切就簡單了。首先我們從來源BPMN模型中找到所有Lane元素以及Task元素,接著把Lane元素轉換Actor元素,把Task元素轉換為UseCase元素,工作就完成了。讓就讓我們解釋一下前面的OCL如何完成它的工作。

一個轉換主進入點可以分為兩個部份,第一個部份稱為init區塊,它的目的主要是宣告變數或是查詢轉換過程需要使用的模型元素。因此在009行中宣告了一個actors變數,它的數值就是來源BPMN模型中所有的Lane元素。為什麼,讓我們看看它使用的OCL代表的意義。009行使用了如下的OCL程式碼:

model.lanes->select(lane | lane.oclIsKindOf(bpmn::BpmnLane))->oclAsType(Sequence(bpmn::BpmnLane)

 

讓我們猜拆解它的語法和009行意如下:

程式碼段

意義

model.lanes

取得來源BPMN模型中所有的Lane物件

->select

由於model.lanescollection 物件,因此使用->代表符號的左邊是collection 物件。Select 類似SQLSelect語法

(lane | lane.oclIsKindOf(bpmn::BpmnLane))

宣告一個暫時變數lane,這個lane如果是bpmn中的BpmnLane物件,也就是我們需要的Lane物件,它的代表符號是bpmn::BpmnLane。那麼就符合select條件而進入結果物件資料集中(object resultset)

->oclAsType(Sequence(bpmn::BpmnLane)

 

由於->select(lane | lane.oclIsKindOf(bpmn::BpmnLane)) collection 物件,因此使用->代表符號的左邊是collection 物件。

oclAsTypeOCL的函式,這個函式把(bpmn::BpmnLane轉換為OCLSequence資料型態

 

瞭解了009行的意義之後,您應該可以瞭解010行的意義。

 

在主進入點的init區塊之後就是主進入點的主要部份了,也就是主進入點回傳的結果物件。這第二個區塊是由object {}包圍的。在object {}區塊中,object就代表回傳的uml::together::Model模型,那麼回傳的模型中包含什麼東西呢?這就是由在object {}區塊中的OCL程式碼來決定的,也就是013014行的執行結果。看看013行在做什麼?

013            ownedMembers := actors->collect(a | makeActor(a))->asOrderedSet();

 

程式碼段

意義

ownedMembers

結果模型之中的特性值,代表結果模型中擁有的元素

:=

指定符號,把:=符號右邊的執行結果指定給:=符號左邊的特性值

actors->

009行宣告的變數,它是collection 物件,因此使用->代表符號的左邊是collection 物件

collect(a | makeActor(a))

 

collectOCL的函式,它把()中的執行結果形成一個collection 物件。而a是暫時變數,這個變數呼叫makeActor對映函式

->asOrderedSet();

把這行程式碼的執行結果轉換為一個有次序的結果collection 物件。

 

019行的makeActor是一個對映函式,它接受一個bpmn::BpmnLaneLane物件並且回傳一個UML 20UseCase模型中的  Actor物件。

 

這整個轉換流程可以使用下面的圖形來說明:

 

由這個範例可以看到OCL語言強大的力量,Together 2006提供的OCL支援包含了執行OCL轉換能力,除錯OCL轉換程式,更提供了MetaModel類似Code Insight的功能,可以幫助開發人員在撰寫OCL轉換程式時對於模型和語法的幫助。例如下圖就是在Together 2006OCL編輯器中打入model.之後,Code Insight視窗便可以出現指引開發人員在之後能夠撰寫的程式碼。

 

除了BPMN模型之外,Together 2006也提供了其他廣泛模型轉換的能力,例如我們也可以對EclispeEMF模型進行各種不同的轉換,再結合EMF產生Java程式碼的能力最終把PIM轉換為JavaPSM

 

1 則迴響