基本資料型別

isoa發表於2009-05-07

XML訊息交換是大多數web服務的基礎,包括SOAP和REST方式。使用XML導致了一些缺陷,包括效能的潛在問題,但是這也提供了抽象層,允許參與交換各方之間的鬆耦合。為了使鬆耦合真的起作用,你需要定義交換中的XML文件的結構,便於驗證文件的正確性。W3C的XML Schema定義語言(在本文後面部分簡寫為“模式”)是用於訊息結構定義的最常用的方法。

大多數web服務不與XML文件直接互動,而是通過web服務工具包的資料繫結轉換層。這方便了應用開發人員,因為這意味著他們可以直接使用選定程式語言的資料結構。但是資料繫結過程需要處理模式資料型別、結構和程式語言資料型別、結構之間的不匹配問題,這些都會為應用製造麻煩。如果你想要你的web服務提供一致的、平臺相容性(這也是使用web服務的首要意義所在),你需要設計模式定義以避免潛在的問題——或者至少意識到使用存在問題的模式的風險。

在本系列文章中,我們將探討一下來自於模式和web服務資料繫結之間不匹配問題的各個方面。在第一篇文章中,我們從最基礎的角度開始,看一看簡單資料型別和相關的問題。

數字表示

數字是業務資料中最基本的型別。既然數字如此重要,你可能會認為模式在這一領域運轉的很穩定和一致。從某種抽象意義上來說,的確是這樣——但是當web服務工具包應用模式時,你仍然會遇到多種問題。

其中一個問題是內建模式數字型別的多樣性。圖1展示了本領域模式資料型別的組成部分。為了便於理解,運用特殊化方法——這棵樹從上到下的分支走得越遠,資料型別就越特殊化。在頂層,是anySimpleType型別,其下面是基本的資料型別float、decimal和double。Float和double是最終型別,符合IEEE對浮點書的標準定義,也提供了跨web服務的良好互操作性:每一個主流程式語言都支援32位浮點數字,匹配浮點數規範,支援64位浮點數字,匹配double模式規範,因此,web服務工具包可以簡單的把這些型別對映到原生語言型別。可能在特殊值(非數字、正無窮、負無窮、正零、負零)的程式語言文字表達上和模式之間有細微的區別,不過工具包可以很容易的處理轉換。

圖1. 模式數字型別

當你來到decimal分支的時候,你就會遇到問題。Decimal本身被定義為一個任意長度小數位數的字串,包括可選的正負號和小數點。Integer是decimal的直接子節點,表示decimal的一個子集,包含可選的正負號,但不允許小數點。Integer的子節點做了進一步約束,nonPositiveInteger和nonNegativeInteger限制了其值大於零或者小於零,long則把取值範圍限制在64位2進位制補碼錶示。Int、short和byte進一步限制了範圍,分別是32位、16位和8位2進位制補碼錶示,而unsigned各種變形則是同樣位數的非負值。

所有主流程式語言支援匹配long、int和short模式型別的值,但是其他變形則有可能出現問題。舉例來說,Java不包含對應unsignedLong和unsignedInt的內建資料型別。Java web服務框架一般使用特殊的類而不是原生型別來應付這種語言的缺失,但是這使得web服務介面多少有些笨拙,同時可能引起效能問題(因為計算時內建型別一般遠遠快於物件型別)。

甚至decimal和integer也會引起問題。大多數Java工具包使用標準的java.lang.BigDecimal和java.lang.BigInteger類來處理這些型別,導致了糟糕的效能,但是支援無大小限制的值。.Net則使用128位固定長度的表示法,限制了值域(模式規範所許可的),但是提供了相對較好的效能。

模式數字型別令人困惑和不協調(比如,為什麼存在nonPositiveInteger而沒有nonPositiveDecimal型別?),一般只是用於某種語法上的便利(因為範圍可以通過simpleType限制實現)。鑑於此,在你的模式定義中,最好避免使用大多數型別,特別是那些用於web服務的。儘量使用某些特定的型別(double和float用於實數,long和int用於整數),因為這些與程式語言的原生型別保持一致。如果你需要使用超過這些型別範圍或者精度的值,請記住decimal和integer由於依賴實現可能無法提供你所想要的,你可以考慮使用字串,在應用程式程式碼中處理這種轉換。

時間問題

時間相關的值是使用模式的另一個常見問題來源。模式定義了九種獨立的時間相關的資料型別,全部基於Western Gregorian日曆形式。不像數字值,時間相關的型別彼此之間不是特殊化的關係——相反,它們都是直接繼承自通用型別anySimpleType。

應用最廣泛的時間型別是dateTime、date和time。這三種資料型別共享同一種表示形式,dateTime最完整。下面是一個dateTime值的例子,我寫這篇文章的當前時間是:"2008-09-08T15:38:53"。Date使用相同的表示法,只是去掉了‘T’和後面的小時——分鐘——秒(在本例中,剩下"2008-09-08"),time值則去掉了“T”之前的所有東西,只保留小時——分鐘——秒("15:38:53")。

到目前為止很簡單,是吧?讓人感到困難的地方在於這些值的實際解釋。日期和時間根據你所在的地點而變,也就是時區。舉例來說,當我在新英格蘭州寫這篇文章時,比格林尼治時間提前12小時,比太平洋西部時間(美國西海岸所用時區)提前19小時。當前時間的dateTime值是"2008-09-08T15:38:53",西雅圖時間則是"2008-09-07T20:38:53"。

很多程式需要指定日期和時間,允許把一個值關聯到另一個。模式通過允許使用附加時區提示的手段滿足這種需求。這種時區提示既可以使用字母‘Z’表明是格林尼治時間,也可以使用相對格林尼治時間的偏移量。因此,所有dateTime值(和很多其他變形)都可以用於指示同一時間:"2008-09-08T15:38:53+12:00"、"2008-09-07T20:38:53-08:00"或者 "2008-09-08T03:38:53Z"。

但是模式並不強制你指定時區提示,沒有這種限制,日期/時間只能被解釋為世界上任意地區的準確時間。對於某些程式,這種性質可能恰好是你想要的——舉例來說,某人的生日日期通常被視為一個特定的日子而不管是哪個地區,同樣的,人們以世界各地的當地時間為準慶祝羅馬制新年——但是對於其他程式,就會產生大麻煩。考慮電話會議的例子,所有參與各方都需要調整會議時間到本地時間。

不幸的是,模式不允許你區分何時需要完全指定的日期/時間、何時需要無分割槽的值(至少不是通過web服務工具包解釋的方式——你可以使用simpleType限制,但通常會被工具包忽略)。因此模式在這一點上的含糊不清意味著工具包需要處理存在和不存在時區提示的兩種值。

這種處理兩種值的需求在解釋階段讓人很頭痛,特別是因為程式語言一般基於絕對時間值實現date/time型別。沒有辦法把缺少時區提示的模式值正確的轉化為絕對時間。當然,這無法阻止工具包處理類似值。在大多數情況下,他們把這些值當作本地時區值轉化,這通常也是你所期望的——但是,當它不是時,由此導致的問題很難定位。

由於時區造成的問題對於date型別特別麻煩。多數情況下,人們認為日期是日曆上的一個固定刻度。舉例來說,當你簽署法律檔案時,你一般會寫上日期。如果你同意一個新專案,一般會有一個計劃完成日期(通常都是空想)。買東西時有時需要出示駕照表明年齡,店員會檢視你的生日日期,並與年齡限制作比較。在所有這些情況下,日期被認為具有日期分辨功能,時區之間的差別被忽略了。但是,模式日期型別使用相應的時區提示,如dateTime和time型別。這種做法分割了模式日期和常用日期。一般來說,這會通過把日期轉化為00:00(午夜,一天的開始時刻)時間表示法來解決,不管是什麼時區。但是如果你把日期值使用本地時區列印出來,你可能會發現它與文件裡最初指定的值不同。

如果模式分別定義了帶有時區提示和不帶時區提示的日期\時間型別,應用程式就會易於挑選其所需的。否則,工具包難以處理一個模式上存在缺陷的日期/時間表示法。Java的JAXB 2.0採用了可能是最綜合的辦法來處理這個問題,通過一個特殊的類(javax.xml.datatype.XmlGregorianCalendar)來處理所有模式日期/時間型別。這種方法儲存了表示法的所有細節,只是把解釋過程交給了開發人員。其他工具包一般使用預設辦法,例如假定是本地時區。

既然有這麼難解的問題,最好的處理辦法是隻為那些完全指定時區提示的值使用日期/時間型別,並保證生成的所有文件都包含時區提示。大多數web服務工具包會自動為你生成時區提示,所以最後一步很簡單。要求輸入文件也使用時區提示則困難些,特別是因為文件會經過多步處理。如果你想要不陷入錯誤的轉化假定所引起的問題中,最好的解決辦法是在模式表達中使用字串型別,這樣的話,web服務工具包會把值傳遞給你的應用程式程式碼,而不會解釋其值。

如果你需要無時區的日期/時間值(如生日日期),最好也使用字串型別。從提供一個精確表示的角度看,這不是很令人滿意,但是避免了web服務工具包把無分割槽的值解釋成本地時區。

引用

應用程式內部使用的資料結構經常包含元件之間的多個連線,包括交叉引用和間接關聯。另一方面,XML天生是樹形結構的。在XML中,通過包含可以很容易的表示一對多的關係,但是表示其他關係則困難。甚至是一對多關係也效率低下。考慮一個文件列舉了顧客的訂單歷史,每一訂單都有對應的付款和傳送地址,但是這些地址通常是重複的。如果你對每一份訂單都插入地址,則最終文件裡會有大量冗餘資訊。

引用可以用於解決XML樹形結構的限制。引用的思想是在XML文件中定義事物一次,包含一個唯一標識碼。每當其他資料想要使用該定義時,使用標識碼建立一個引用。

模式直接支援兩種引用。第一,使用ID型別,定義元素識別符號,可以使用IDREF或者IDREFS型別從文件中任意處關聯。ID/IDREF關聯的優點是非常簡單——識別符號就是名字,任何型別的元素都可以在模式中定義ID值。缺點是使用全域性上下文,因此沒辦法說某個IDREF使用的值肯定採用特定元素型別定義,用作ID值的名字在文件中必須唯一(即使是跨型別的元素)。一些web服務工具包支援使用ID/IDREF關聯來表示資料結構內部的引用(包括 JAX-WS/JAXB 2.0,Apache Axis2使用JiBX資料繫結時),其他工具包(如.Net、Axis2使用ADB)則不是,把IDREF值簡單的當作文字字串。

模式支援的第二種引用是key/keyref關聯。ID/IDREF關聯使用資料型別定義,key/keyref關聯則是模式定義的結構組成部分。這允許key/keyref關聯比ID/IDREF更具有表現力,包括定義key值唯一的上下文環境。但是,因為key/keyref關聯的設計意圖更傾向於文件驗證而不是結構化,它們太複雜,資料繫結框架一般也不會用於做XML資料和資料結構之間的轉化。

因此,如果你想在XML文件中插入關聯並由web服務工具包處理,唯一的辦法就是ID/IDREF。一些工具包現在直接支援這種關聯,其他的則只是把識別符號看做字串,但是你可以編寫應用程式程式碼來交叉引用識別符號和值、構建自己的關聯。

結論

在本文中,我們探討了在web服務中使用最常用資料型別時會遇到的問題。除了本文提到的還有很多其他模式資料型別(一共42種!),其中一些會引起其它問題。總的來說,使用web服務模式定義的最好辦法是避免使用過度特殊化的型別(除了那些匹配常用程式語言型別的數字型別),當你想完全控制這些值時,你可以使用字串型別轉換這些值。

值得一提的是,雖然本文中討論的一些問題可以通過資料繫結框架處理的更好一些,但是很多問題來源於模式本身。特別是,資料/時間型別難以使用,甚至可能因為缺少時區和非時區之間的區別來導致錯誤。雖然可以像JAXB那樣使用XmlGregorianCalendar類把這種混亂交給使用者處理,但這實在不是一種解決辦法。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/14780828/viewspace-594424/,如需轉載,請註明出處,否則將追究法律責任。

相關文章