REST service 化一个数据系统(REST Service 的最佳实践,第 2 部分)
引言
在 Web 變成可編程的 platform 的進程中,有一些應(yīng)運而生的新的服務(wù)的應(yīng)用場景。我們來看一個具體的例子。Lily 是 Web 2.0 team 一個 Web 開發(fā)人員,她想買一些 Web 2.0 開發(fā)相關(guān)的書來開闊和加深她對 Web 2.0 技術(shù)的理解。她希望怎么做呢?她打開她訂閱的 eBay暢銷書服務(wù)(一個 Feed),看到目前最暢銷的一組關(guān)于 Web 2.0 的書籍,在她瀏覽這些書的介紹的時候,她還想看看這些書在 Amazon上的書評。等她決定好了買哪些書的時候,她想最好能貨比三家,她要知道各個書商提供的 書的價格比較,她選擇了一家性價比比較合理的書商后需要付錢購買,比如用 Goolge Check Out 來付賬。服務(wù)之間的導(dǎo)航關(guān)系如圖 1 所示。
圖 1. 買書所需用的的服務(wù)以及關(guān)系
今天,lily 如果想做到這一點,要么 IT developer 幫她專門開發(fā)一個集成的系統(tǒng),把這些不同 vendor 提供的 Web 服務(wù)集成起來:eBay 提供的 暢銷書服務(wù),Amazon 提供的 書評服務(wù),directtextbook.com 提供的 書的價格比較 服務(wù),Google 提供的 Goolge Check Out 服務(wù)。但顯然這種方法實施性和適應(yīng)性都較差,因為 lily 的需求不固定,瀏覽的路徑也不固定,IT developer 很難決定需要集成哪些 Web 服務(wù),很難滿足像 lily 這樣的不固定的需求。另外,Lily 還可以選擇使用現(xiàn)有的 Mashups 的產(chǎn)品,如 IBM Mashup Center,但是她需要做比較枯燥乏味而又不是那么容易的 widget 之間的 wiring,而且她還要自己知道這些個相關(guān)的 Web 服務(wù)。對 lily 而言,如果有這樣一個系統(tǒng),她可以從最開始的 eBay 暢銷書服務(wù)開始瀏覽,然后查看 Amazon 的書評,就像現(xiàn)在的 Web 上面的 HTML 頁面之間通過 hyperlink 瀏覽一樣,她也可以很容易的通過鼠標的點擊、選擇、輸入一些很少的東西再輔以搜索就可以從一個服務(wù)達到另一個服務(wù),平滑、自然、簡單、輕松。
這是一個美好的夢想,如何能實現(xiàn)這個夢想?作為第一步,每個企業(yè)或者業(yè)務(wù)系統(tǒng)都必須服務(wù)化,實現(xiàn)數(shù)據(jù)的靈活訪問,也就是企業(yè)信息系統(tǒng)的解鎖,讓普通人以 Web 的方式就能輕松的訪問。基于此,我們分析 lily 買書這個場景中的幾個 Web 服務(wù)可知,這些 Web 服務(wù)雖然來自不同的系統(tǒng),但他們之間是有聯(lián)系的。eBay 提供的暢銷書服務(wù)的數(shù)據(jù)里面包含書的 ISBN 信息,而 Amazon 的書評服務(wù)和 directtext.com 提供的書的價格比較服務(wù)都需要 ISBN 信息才能返回相應(yīng)的書的書評和價格比較。另外如果現(xiàn)在很多人都用 Goolge Check Out 付賬,說明他的 popularity 比較高。當(dāng)用戶想要購買的時候,可以用它。如果我們要滿足 lily 的要求而又不需要 IT developer 的參與,我們就需要一種方式描述和建立服務(wù)之間的關(guān)聯(lián),發(fā)現(xiàn)和利用這種關(guān)聯(lián)來改善普通人們使用互聯(lián)網(wǎng)的體驗 – 像使用 HTML 為基礎(chǔ)的內(nèi)容 Web 一樣來自由地從一個服務(wù)瀏覽到另外一個服務(wù),這正是 REST 架構(gòu)風(fēng)格可以解決的問題。
傳統(tǒng)的 Web 業(yè)務(wù)系統(tǒng)的分析
在軟件體系架構(gòu)設(shè)計中,分層式結(jié)構(gòu)是最常見,也是最重要的一種結(jié)構(gòu)。Martin Fowler 在《 Patterns of Enterprise Application Architecture 》一書中,將整個架構(gòu)分為三個主要的層:表示層、領(lǐng)域?qū)雍蛿?shù)據(jù)源層,如圖 2 所示。
表示層 (User Interface Layer) 位于最上層,離用戶最近,為用戶提供一種交互式操作的界面,用于顯示數(shù)據(jù)和接收用戶輸入的數(shù)據(jù)。
業(yè)務(wù)邏輯層(Business Logic Layer)是系統(tǒng)架構(gòu)中體現(xiàn)核心價值的部分。它的關(guān)注點主要集中在業(yè)務(wù)規(guī)則的制定、業(yè)務(wù)流程的實現(xiàn)等與業(yè)務(wù)需求有關(guān)的系統(tǒng)設(shè)計,也即是說它是與系統(tǒng)所應(yīng)對的領(lǐng)域(Domain)邏輯有關(guān),很多時候,也將業(yè)務(wù)邏輯層稱為領(lǐng)域?qū)印I(yè)務(wù)邏輯層在體系架構(gòu)中的位置很關(guān)鍵,它處于數(shù)據(jù)訪問層與表示層中間,起到了數(shù)據(jù)交換中承上啟下的作用。
數(shù)據(jù)訪問層(Data Access Layer)有時候也稱為是持久層,其功能主要是負責(zé)數(shù)據(jù)庫的訪問,直接操作數(shù)據(jù)庫,針對數(shù)據(jù)的增、刪、改、查。簡單的說法就是實現(xiàn)對數(shù)據(jù)表的 Select,Insert,Update,Delete 的操作。如果要加入 ORM 的元素,那么就會包括對象和數(shù)據(jù)表之間的 mapping,以及對象實體的持久化。?
分層的結(jié)構(gòu)給 Web 應(yīng)用的開發(fā)帶來了很多好處,比如開發(fā)人員可以只關(guān)注整個結(jié)構(gòu)中的其中某一層;可以很容易的用新的實現(xiàn)來替換原有層次的實現(xiàn);可以降低層與層之間的依賴;有利于標準化;利于各層邏輯的復(fù)用。現(xiàn)在也是作為 Web 應(yīng)用的主流架構(gòu)提供。
圖 2. Web 應(yīng)用的三層架構(gòu)
企業(yè)信息系統(tǒng)面臨的挑戰(zhàn)
盡管企業(yè)現(xiàn)在有很多的數(shù)據(jù)系統(tǒng)和應(yīng)用系統(tǒng),但是還仍然面臨以下的嚴峻挑戰(zhàn):
以更靈活的 Mashup 的視角看業(yè)務(wù)系統(tǒng)
Mashup 和現(xiàn)有的 Web 應(yīng)用系統(tǒng)
Mashup 是 Web 2.0 領(lǐng)域里面一個特別火的詞,wikipedia 上的解釋是“網(wǎng)絡(luò)聚合應(yīng)用,由一個或者多個信息源整合起來的網(wǎng)站或者網(wǎng)絡(luò)應(yīng)用”。從企業(yè)的角度看 Mashup,應(yīng)該理解成更“靈活的數(shù)據(jù)的使用和更簡單的應(yīng)用的構(gòu)建”。圖 3 是一個“客戶 360 度信息”的 Mashup。可以看出,在這個 Mashup 中包含五個服務(wù),分別是:①以表格形式展示的客戶的基本信息;②以曲線形式展示的沃爾瑪?shù)墓善毙畔?#xff1b;③以時間線形式展示的沃爾瑪?shù)馁徺I行為;④以柱狀圖形式展示的客戶季度收入情況;⑤以 feed 閱讀器形式展示的沃爾瑪?shù)男侣劇?梢钥闯?#xff0c;這個 Mashup 里面包含的服務(wù)來自好幾個數(shù)據(jù)源:①客戶的基本信息來自企業(yè)的 CRM 系統(tǒng);②股票信息來自 google 財經(jīng);③客戶的購買行為來自企業(yè)的采購系統(tǒng);④客戶季度銷售額來自 google 財經(jīng);⑤新聞來自 google news。
圖 3. 一個“客戶 360 度信息”的 Mashup
從上面的例子我們可以看出 Mashup 和現(xiàn)有的 Web 應(yīng)用系統(tǒng)相比的優(yōu)勢:
Mashup 和傳統(tǒng)系統(tǒng)集成技術(shù)
Mashup 是 Web 2.0 領(lǐng)域里面一個特別火的詞,wikipedia 上的解釋是“網(wǎng)絡(luò)聚合應(yīng)用,有一個或者多個信息源整合起來的網(wǎng)站或者網(wǎng)絡(luò)應(yīng)用”。從企業(yè)的角度看 Mashup,應(yīng)該理解成“更靈活的數(shù)據(jù)的使用和更簡單的應(yīng)用的構(gòu)建”。那很多人要問了:從這個角度講,Mashup 和傳統(tǒng)的 BPM、BI、EII、ESB 類似的集成技術(shù)有啥不一樣呢?我們來分別看一看。
相對于這些傳統(tǒng)的企業(yè)集成技術(shù),Mashup 是一種擴展和補充。Mashup 提供更靈活的數(shù)據(jù)使用和展示,主要關(guān)注情景式的、瞬態(tài)的應(yīng)用,就像在引言部分 lily 買書的例子一樣。傳統(tǒng)的這些集成系統(tǒng)也可以以 Web 服務(wù)的形式為 Mashup 提供強大的企業(yè)數(shù)據(jù)源。
Mashup 的解決方案
前面兩個小節(jié)分析了 Mashup 和現(xiàn)有 Web 應(yīng)用系統(tǒng)以及傳統(tǒng)信息集成集成的優(yōu)缺點,這節(jié)主要講述以 Mashup 技術(shù)為基礎(chǔ)的解決方案。在業(yè)務(wù)人員能夠創(chuàng)建 Mashup 應(yīng)用之前,需要把信息和服務(wù)發(fā)布成為可以 Mashup 的格式,通常而言就是 Feeds 或者 Widgets。
圖 4. 基于 Mashup 的解決方案概念圖
最佳實踐— REST 服務(wù)化現(xiàn)有系統(tǒng)
RESTify 數(shù)據(jù)層
這一節(jié)主要講述識別、創(chuàng)建和發(fā)布數(shù)據(jù)服務(wù)的方法。
識別數(shù)據(jù)服務(wù)
識別數(shù)據(jù)服務(wù)是最關(guān)鍵的一步,主要解決針對一個數(shù)據(jù)系統(tǒng),應(yīng)該提供哪些數(shù)據(jù)服務(wù)。根據(jù)該系列的第一篇文章“REST Service 的最佳實踐 第一部分:重新解析 REST Service”,讀者已經(jīng)知道,RESTful Web 服務(wù)的核心是以“資源”為中心,而這里實體 - 關(guān)系圖中的“實體”和“資源”在語義上有很大的關(guān)聯(lián)性,所以這里提供一個基于 E-R(Entity-Relationship)模型的識別數(shù)據(jù)服務(wù)的方法論。實體 - 關(guān)系圖是一個在數(shù)據(jù)庫設(shè)計時幫助架構(gòu)師進行思考的重要的概念圖,反映出信息系統(tǒng)的實體以及實體和實體間的關(guān)系,因此實體 - 關(guān)系圖一個很好的手段去發(fā)現(xiàn)曝露出去的資源。圖 5 是一個在線購物網(wǎng)站的 E-R 圖,我們將以此為例,講述識別服務(wù)的方法。
圖 5. 一個在線購物網(wǎng)站的實體 - 關(guān)系圖
識別數(shù)據(jù)服務(wù)的步驟如下:
如圖 5 中所以,黃色的方框表示的是“實體”。本質(zhì)上,這些“實體”對應(yīng)的是系統(tǒng)的“資源”,可以看出,一個在線購物網(wǎng)站,需要提供的資源包括:買家、賣家、供貨商、商品、訂單、賬單、快遞單、購物車、買家評價、分類。
實體 - 關(guān)系圖中定義的“實體”之間的“關(guān)系”為“一對一”、“一對多”、“多對多”的關(guān)系。把實體 - 關(guān)系圖中的“關(guān)系”發(fā)布成數(shù)據(jù)服務(wù),這里所說的“關(guān)系”不是實體之間的數(shù)量對應(yīng)關(guān)系,而是語義上數(shù)據(jù)依賴、相似關(guān)系。實體之間的語義關(guān)系有幾種,如圖 6 所示。
圖 6. 實體之間的語義關(guān)系圖
下面分別來闡述圖 6 所示的關(guān)系對應(yīng)的數(shù)據(jù)服務(wù)。
相同屬性
“相同屬性”指的是“實體 A 和 B 有相同屬性”。在“在線購物”的這個場景中,由“相同屬性”找出來的數(shù)據(jù)服務(wù)可能包括:具有相同“收獲地址”的訂單;具有相同“風(fēng)格”的商品,具有相同屬性“打折商品”的商品,等等。一個應(yīng)用場景是:當(dāng)用戶瀏覽一件 T 恤的時候,假如“T 恤”這個實體的屬性有:風(fēng)格、面料、尺寸、品牌這四個屬性,我們可以識別出來跟實體“T 恤”相同屬性的數(shù)據(jù)服務(wù)有兩類:一類是相同實體類型的,如相同“風(fēng)格”的“T 恤”,相同“面料”的“T 恤,相同“尺寸”的“T 恤”,相同“面料”的“T 恤”;還有不同實體的具有“相同屬性”的數(shù)據(jù)服務(wù)包括:相同“風(fēng)格”的“褲子”,相同“面料”的“褲子”,相同“尺寸”的“褲子”,相同“面料”的“褲子”等等。
從技術(shù)來講,“相同屬性”這種識別出來的數(shù)據(jù)服務(wù)其實就是給在 1)中識別出來的實體創(chuàng)建了很多不同的查詢條件。通過“屬性”,把數(shù)據(jù)服務(wù)關(guān)聯(lián)了起來。通過本系統(tǒng)的第一篇文章,讀者已經(jīng)知道了 REST 的一個核心思想是創(chuàng)建相互聯(lián)系的服務(wù),而“相同屬性”識別數(shù)據(jù)服務(wù)的方法,一方面我們可以得到很多數(shù)據(jù)服務(wù),另一方面,這些數(shù)據(jù)服務(wù)天然的就相互聯(lián)系在一塊,和 REST 的核心思想一致。
從用戶的角度來講,“相同屬性”是一種導(dǎo)航的線索,通過“相同屬性”的關(guān)系識別出來的數(shù)據(jù)服務(wù),使得人們獲得一種靈活查詢數(shù)據(jù)的能力。
操作
“操作”關(guān)系是指“實體 A 對實體 B 做了什么操作”。比如在“在線購物”的這個場景中,“買家”是一種類型的實體,“商品”是另一種類型的實體,“買家 A ‘購買’了商品 B”就是“操作”關(guān)系的一種實例。
通過“操作”關(guān)系,可以定義的數(shù)據(jù)服務(wù)包括:買家 A 購買的商品列表,買家 A 瀏覽的商品列表,買家 A 評價的商品列表等等。
和通過“相同屬性”識別的數(shù)據(jù)服務(wù)類似,通過“操作”關(guān)系識別出來的數(shù)據(jù)服務(wù)也天然的符合 REST 的約束。
實體 A 的實例和實體 B 有相同的關(guān)系
“實體 A 的實例和實體 B 有相同的關(guān)系”指的是同種類型的實體的不同實例和第二種類型的實體有相同的關(guān)系。還是拿“在線購物”為例,買家 A“購買”了商品 C,買家 B 也“購買”了商品 C,那么可以提供一個數(shù)據(jù)服務(wù)為“同時購買商品 C”的買家列表。細心的讀者已經(jīng)發(fā)現(xiàn),這種關(guān)系和“相同屬性”的關(guān)系有相似之處,不同點在于“相同屬性”的關(guān)系不依賴于第二種實體,而這種關(guān)系依賴于外來的實體,識別數(shù)據(jù)服務(wù)的規(guī)則是一樣。
和前兩種通過“關(guān)系”識別出來的數(shù)據(jù)服務(wù)一樣,通過“實體 A 的實例和實體 B 有相同的關(guān)系”的關(guān)系識別出來的數(shù)據(jù)服務(wù)也天然的符合 REST 的約束。
通過前兩種通過“實體”和“關(guān)系”的方法,我們已經(jīng)得到了很多 RESTful 的數(shù)據(jù)服務(wù),但這并沒有把所有的數(shù)據(jù)服務(wù)都找到,還有一類隱含的數(shù)據(jù)服務(wù)。在“在線購物”的場景中,“買家”(一種實體 - 關(guān)系圖中的實體)可以對“商品” (一種實體 - 關(guān)系圖中的實體)進行“評價”(一種實體 - 關(guān)系圖中的關(guān)系),過程中產(chǎn)生了一個隱含的實體“評價內(nèi)容”。
也就是說,在實體 - 關(guān)系圖中的“關(guān)系”,除了像 CRUD(創(chuàng)建、查看、更新、刪除)這些直接作用在另一個實體的關(guān)系以外,還有像“評價”、“打分”等關(guān)系,這些關(guān)系會產(chǎn)生一些新的實體。我們需要分析實體 - 關(guān)系圖中的關(guān)系,識別出這些隱含的數(shù)據(jù)服務(wù)。
創(chuàng)建和發(fā)布數(shù)據(jù)服務(wù)
現(xiàn)在有很多平臺可以用來創(chuàng)建數(shù)據(jù)服務(wù),IBM 就提供了幾個選擇,例如 IBM WebSphere sMash, Web 2.0 Featurepack for WAS 和 Infosphere Mashuphub 等。利用這些平臺,開發(fā)人員可以很容易的利用腳本語言或者 Java,或者通過 Mashuphub 的 plugin 來創(chuàng)建這些數(shù)據(jù)服務(wù)。下面以 Mashuphub 為例,來簡要的說明創(chuàng)建數(shù)據(jù)服務(wù)的方法。
首先假設(shè)“在線購物”的數(shù)據(jù)庫 schema 設(shè)計(部分)如清單 1 所示:
清單 1.“在線購物”的數(shù)據(jù)庫 schema 設(shè)計(部分)
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | create table Buyers( ?????id INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY, ?????name varchar(64) NOT NULL, ?????gender varchar(10), ?????salary INTEGER, ?????email varchar(50), ?????phonenumber varchar(50), ?????location varchar(256),?? ?????starlevel double NOT NULL default 0,?? ?????PRIMARY KEY (id) ???); create table Sellers( ?????id INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY, ?????name varchar(64) NOT NULL, ?????phonenumber varchar(50), ?????location varchar(256),?? ?????starlevel double NOT NULL default 0,?? ?????PRIMARY KEY (id) ???); create table Delivery( ?????id INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY, ?????deliverto varchar(256) NOT NULL, ?????consignee varchar(50), ?????consignee_phonenumber varchar(50), ?????delivery_company varchar(256), ?????PRIMARY KEY (id) ???); create table Orders( ?????id INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY, ?????buyerid INTEGER NOT NULL, ?????sellerid INTEGER NOT NULL, ?????deliveryid INTEGER NOT NULL, ?????generatetimestamp INTEGER default 0, ?????confirmtimestamp INTEGER default 0, ?????paymenttimestamp INTEGER default 0, ?????payment double NOT NULL default 0,??????? ?????PRIMARY KEY (id), ?????FOREIGN KEY (buyerid) REFERENCES Buyers(id), ?????FOREIGN KEY (sellerid) REFERENCES Sellers(id), ?????FOREIGN KEY (deliveryid) REFERENCES Delivery(id), ???); create table Books( ?????id INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY, ?????title varchar(128) NOT NULL, ?????author varchar(128), ?????manufactor varchar(128), ?????averageRating? double, ?????price double, ?????PRIMARY KEY (id) ???); create table TShirts( ?????id INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY, ?????name varchar(64) NOT NULL, ?????color varchar(64), ?????tsize varchar(10), ?????brand? varchar(64), ?????fabric varchar(64), ?????style varchar(64), ?????PRIMARY KEY (id) ???); create table Trousers( ?????id INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY, ?????name varchar(64) NOT NULL, ?????color varchar(64), ?????tsize varchar(10), ?????brand? varchar(64), ?????fabric varchar(64), ?????style varchar(64), ?????PRIMARY KEY (id) ???); create table Scarves( ?????id INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY, ?????name varchar(64) NOT NULL, ?????color varchar(64), ?????tsize varchar(10), ?????brand? varchar(64), ?????fabric varchar(64), ?????style varchar(64), ?????PRIMARY KEY (id) ???); create table Shoes( ?????id INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY, ?????name varchar(64) NOT NULL, ?????color varchar(64), ?????tsize varchar(10), ?????brand? varchar(64), ?????fabric varchar(64), ?????style varchar(64), ?????PRIMARY KEY (id) ???); |
接下來用 IBM Infosphere Mashuphub 來把 Database 里面的數(shù)據(jù)服務(wù)化。Mashuphub 可以把各種各樣的企業(yè)數(shù)據(jù)源發(fā)布成數(shù)據(jù)種子(Feed),詳細的介紹請參考 Mashuphub info center。這里簡單的介紹下用 Mashuphub 把數(shù)據(jù)庫的數(shù)據(jù)發(fā)布成種子。有以下幾步:
圖 7. 選擇數(shù)據(jù)源
圖 8. 配置 JDBC 數(shù)據(jù)源
圖 9. 用 SQL 生成器生成 SQL
圖 10. 提供一些描述性的信息
圖 11. 發(fā)布成功以后
RESTify 展示層
表示層主要負責(zé)數(shù)據(jù)的展示,我們稱之為 viewer。開發(fā) Viewer 需要具有較多的 Web 開發(fā)的技巧,例如 HTML,JavaScript 和 CSS 等。為了重用數(shù)據(jù)的展示,和數(shù)據(jù)層一樣,我們也需要模塊化,REST 服務(wù)化。現(xiàn)在流行的可重用的 Web 小組件有很多的規(guī)范,像 google gadget,IBM iWidget 等等。下面以 iWidget 規(guī)范為例,來展示一個 viewer 的開發(fā)過程。下面一個 widget 為例,簡要的介紹,結(jié)合數(shù)據(jù)服務(wù),widget 的開發(fā)過程。
下面以一個簡單的用來顯示亞馬遜圖書搜索的數(shù)據(jù)服務(wù)的 viewer。圖書搜索數(shù)據(jù)服務(wù)提供的數(shù)據(jù)如清單 2 所示:
清單 2. 圖書搜索服務(wù)的數(shù)據(jù)樣本
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <entry xmlns:aws="http://webservices.amazon.com/AWSECommerceService/2005-10-05"> <title> Don't Make Me Think: A Common Sense Approach to WebUsability </title> <aws:ASIN> 0321344758 </aws:ASIN> <link rel="alternate" href="http://www.amazon.com/Dont-Make-Think-Usability-ebook/dp /B000SEGQNS%3FSubscriptionId%3DAKIAJ3RS7ICEOBT6PH4Q%26tag%3Dws%26linkCode%3Dxm2%26cam p%3D2025%26creative%3D165953%26creativeASIN%3D0321344758"/> <icon> http://ecx.images-amazon.com/images/I/51GRhbtsUQL._SL160_.jpg </icon> <logo> http://ecx.images-amazon.com/images/I/51GRhbtsUQL._SL160_.jpg </logo> <content type="application/xml"> <p:row xmlns:p="http://www.example.com"> <aws:title> Don't Make Me Think: A Common Sense Approach to WebUsability </aws:title> <aws:ASIN> 0321344758 </aws:ASIN> <link rel="alternate" href="http://www.amazon.com/Dont-Make-Think-Usability-ebook /dp/B000SEGQNS%3FSubscriptionId%3DAKIAJ3RS7ICEOBT6PH4Q%26tag%3Dws%26linkCode%3Dxm2%26 camp%3D2025%26creative%3D165953%26creativeASIN%3D0321344758"/> <author> Steve Krug</author> <aws:Manufacturer> New Riders Press </aws:Manufacturer> <aws:lowestNewPrice> $23.30 </aws:lowestNewPrice/> <aws:averageRating> 4.5 </aws:averageRating> <p:image> http://ecx.images-mazon.com/images/I/51GRhbtsUQL._SL160_.jpg </p:image> </p:row> </content> </entry> |
按照 iWidget 的規(guī)范,開發(fā)一個 widget 需要提供 widget 的定義文件,如清單 3 所示:
清單 3. 圖書搜索服務(wù) viewer 的 widget 定義文件
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | <iw:iwidget name="amazonSearchViewer" xmlns:iw="http://www.ibm.com/xmlns/prod/iWidget" ?iScope="common.iwidget.amazonSearchViewer" allowInstanceContent="true" ??supportedModes="view edit" mode="view" lang="en"> <iw:itemSet id="attributes" > <iw:item id="feedURL" value="" readOnly="false"/> <iw:itemDescription name="feedURL" type="url.feed.tabular" description="Customized widget for displaying book list from Amazon"/> <iw:item id="title" value="" readOnly="false"/> <iw:item id="view" value="table" readOnly="false"/> <iw:item id="titleElement" value="title" /> <iw:itemDescription name="titleElement" type="text" description="book title element"/> <iw:item id="authorElement" value="author" /> <iw:itemDescription name="authorElement" type="text" description="book author element"/> <iw:item id="publisherElement" value="Manufacturer" /> <iw:itemDescription name="publisherElement" type="text" ?description="book publisher element"/> <iw:item id="asinElement" value="asin" /> <iw:itemDescription name="asinElement" type="text" description="book isbn element"/> <iw:item id="ratingElement" value="AverageRating" /> <iw:itemDescription name="ratingElement" type="text" description="book rating element"/> <iw:item id="imageElement" value="image" /> <iw:itemDescription name="imageElement" type="text" description="book rating element"/> <iw:item id="priceElement" value="lowestNewPrice" /> <iw:itemDescription name="priceElement" type="text" description="book price element"/> </iw:itemSet> <iw:resource uri="../styles/common.css" /> <iw:resource uri="amazonSearchViewer.js" /> <iw:content mode="view"> <![CDATA[ <div id="_IWID_serviceNode"> <div id="_IWID_loadingNode" ?style="display:none;margin-left:48%;margin-top:40px;height:80px;"> </div> </div> ]]> </iw:content> <iw:content mode="edit"> <![CDATA[ ????]]> </iw:content> </iw:iwidget> |
itemSet 用來描述 widget 的可配置信息,包括 FeedURL,widget 的標題,還包括和數(shù)據(jù)服務(wù)相關(guān)的數(shù)據(jù)項的描述:titileElement 用來描述圖書的標題信息、authorElement 用來描述圖書的作者信息等等。
有幾種方式來定義 widget,清單 3 給出了其中的一種,用來展示一定類型的數(shù)據(jù):圖書標題,圖書作者,圖書標號,圖書最新最低價格等等。這種方式編寫的 viewer 具有一定的普適性和可重用性,只要數(shù)據(jù)服務(wù)提供了這些信息都可以用這個 viewer 來展示。清單 4 給出了另一種 widget 的定義方式。
清單 4. 圖書搜索服務(wù) viewer 的 widget 定義文件 2
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | <iw:iwidget name="amazonSearchViewer" xmlns:iw="http://www.ibm.com/xmlns/prod/iWidget" ?iScope="common.iwidget.amazonSearchViewer" allowInstanceContent="true" ?supportedModes="view edit" mode="view" lang="en"> <iw:itemSet id="attributes" > <iw:item id="feedURL" value="" readOnly="false"/> <iw:itemDescription name="feedURL" type="url.feed.tabular" description="Customized widget for displaying book list from Amazon"/> <iw:item id="title" value="" readOnly="false"/> <iw:item id="view" value="table" readOnly="false"/> </iw:itemSet> <iw:resource uri="../styles/common.css" /> <iw:resource uri="amazonSearchViewer.js" /> <iw:content mode="view"> <![CDATA[ <div id="_IWID_serviceNode"> <div id="_IWID_loadingNode" style="display:none;margin-left:48%;margin-top:40px;height:80px;"> </div> </div> ]]> </iw:content> <iw:content mode="edit"> <![CDATA[ ????]]> </iw:content> </iw:iwidget> |
清單 4 的 itemSet 只描述了數(shù)據(jù)源的 FeedURL,而沒有更多的關(guān)于可配置數(shù)據(jù)項的描述,這種方式定義的 widget 把對數(shù)據(jù)的處理隱含在代碼里面,局限性比較大,可配置性差,可重用性也差。下面需要寫一些 javascript 來處理一個展示的邏輯,如清單 5 所示。負責(zé)創(chuàng)建頁面元素,并發(fā)送 HTTP 請求取回來 feed 的結(jié)果。
清單 5. widget 的邏輯代碼
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | dojo.provide("common.iwidget.amazonSearchViewer"); dojo.declare("common.iwidget.amazonSearchViewer", null, { title:null, serviceURL:null, view:null, onLoad: function(){ this.domID = "_" + this.iContext.widgetId + "_"; var att = this.iContext.getiWidgetAttributes(); ???this.title = att.getItemValue("title"); ???this.serviceURL = att.getItemValue("feedURL"); ???this.view = att.getItemValue("view"); ???? ???var loading = dojo.byId(this.domID + "loadingNode"); ???var innerNode = document.createElement("div"); ???loading.appendChild(innerNode); ???new hyperservice.iwidget.ui.Loading({},innerNode); }, onView: function(){ ???var serviceNode = dojo.byId(this.domID + "serviceNode"); ???var innerNode = document.createElement("div") ???serviceNode.appendChild(innerNode) ???? ??????//fetch the feed, ???var self = this; ???var loadCallbackFunc = function(feed){ ???new iwidget.ui.FeedListViewer ???({feed:feed,viewTitle:feed.title,serviceInstance:self.serviceURL, ???selectedItemViewer:"common.ui.AmazonSearchListViewerItem"},innerNode); ???} ???? ???var errorCallbackFunc = function(data){ ???console.error("failed to fetch a feed with url:"+self.serviceURL); ???console.error(data) ???} ???feedFetcher.fetch(this.serviceURL, loadCallbackFunc,errorCallbackFunc); ???} ???} }); |
在清單 5 中創(chuàng)建 FeedListViewer 的時候有一個參數(shù)是 selectedItemViewer,用來設(shè)置具體的關(guān)于數(shù)據(jù)的展示內(nèi)容部分,具體的實現(xiàn)代碼如清單 6 所示。
清單 6. 關(guān)于數(shù)據(jù)的展示部分的代碼
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | dojo.provide("common.ui.AmazonSearchListViewerItem") dojo.declare("common.ui.AmazonSearchListViewerItem", [dijit._Widget, dijit._Templated],{ imgSrc:null, entry:null, serviceInstance:null, //dojoattachpoint imgNode:null, itemDetailNode:null, titleNode:null, authorNode:null, manufacturerNode:null, //priceMessageNode:null, priceNode:null, ASINnode:null, ratingNode:null, averageRatingNode:null, averageRatingNode:null, constructor:function(){ this.entry = null; }, postCreate:function(){ this.createLiveText(); }, createLiveText:function() { if(!this.entry){ return; } var foundPriceElement = false; this.imgNode.src = this.imgNode; var contextRow = {} dojo.forEach(this.entry.dataitemRows,function(dataitem){ //using xpath ??????????????????????????????????????????????????????// as dataitem identifier contextRow[dataitem.xpath] = dataitem.value; }) dojo.forEach(this.entry.dataitemRows,dojo.hitch(this,function(dataitem){ if(dataitem.name.indexOf("image")!=-1){ this.imgNode.src = dataitem.value }else if(dataitem.name.toLowerCase()=="title"){ this.titleNode.innerHTML = "<span>"+dataitem.value+"</span>" }else if(dataitem.name.toLowerCase()=="author"){ this.authorNode.innerHTML = "<span>"+dataitem.value+"</span>" }else if(dataitem.name.toLowerCase()=="manufacturer"){ this.manufacturerNode.innerHTML = "<span>"+dataitem.value+"</span>" }else if(dataitem.name.toLowerCase() == "lowestnewprice"){ foundPriceElement = true; if(dataitem.value!=""){ ????this.priceNode.innerHTML = "<span>"+dataitem.value+"</span>" ????this.priceNode.style.display = "inline" }else{ this.priceNode.style.display = "none" } }else if(dataitem.name.toLowerCase() == "asin"){ this.ASINnode.innerHTML = "<span>"+dataitem.value+"</span>" }else if(dataitem.name.toLowerCase() == "averagerating"){ var ratio=parseFloat(dataitem.value); if(Math.floor(ratio)==Math.ceil(ratio)) { dojo.addClass(this.ratingNode,"stars"); } var count=0-(5-Math.floor(ratio))*18; var style=count+"px"; dojo.style(this.ratingNode,{ backgroundPosition:style }); this.averageRatingNode.innerHTML="<span>("+dataitem.value+")</span>"; ?????if(!foundPriceElement){ //this.priceMessageNode.style.display = "none" this.priceNode.style.display = "none" } })) } }); .stars{ Background:transparent url(sample/imapges/star.png) no-repeat scroll 0 0; } |
具體的 viewer 的展示效果如圖 12 所示。
按照此種方法,開發(fā)人員可以快速的開發(fā)用來展示各種各樣類型數(shù)據(jù)的 widget。
結(jié)束語
Web 在轉(zhuǎn)化為一個可編程的平臺,越來越多的 Web Service 被發(fā)布出來,它們表現(xiàn)為 Feeds,REST APIs 和 Widgets。據(jù) www.ProgrammableWeb.com 網(wǎng)站的統(tǒng)計,該網(wǎng)站已經(jīng)擁有 1000 多個 Web API 而且以每天新增 2 個的速度在增加。來自 Google 的消息,Google gadget 現(xiàn)在已經(jīng)有 45170? 多個。
本文通過深入的分析 Web 架構(gòu)的業(yè)務(wù)系統(tǒng)所面臨的挑戰(zhàn),即不夠靈活、可復(fù)用性差、業(yè)務(wù)人員難于參與到應(yīng)用的構(gòu)建等等,然后以 Mashup 的角度重新審視已有的業(yè)務(wù)系統(tǒng),帶來一種全新的使用數(shù)據(jù)、業(yè)務(wù)邏輯、和展示層的思路,使得這些企業(yè)積累起來的資產(chǎn)能夠被更好的使用。然后,本文以“在線購物”應(yīng)用為例介紹了以實體 - 關(guān)系模型為基礎(chǔ)的 REST 服務(wù)化已有的業(yè)務(wù)系統(tǒng),包括識別、創(chuàng)建和發(fā)布“數(shù)據(jù)服務(wù)”、識別和創(chuàng)建“展示服務(wù)”。通過這些分析和方法的介紹,希望給需要提高系統(tǒng)的可重用性、靈活性、響應(yīng)更快的開發(fā)人員提供一定的幫助。
總結(jié)
以上是生活随笔為你收集整理的REST service 化一个数据系统(REST Service 的最佳实践,第 2 部分)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 重新解析 REST Service(RE
- 下一篇: 把 SOAP 服务转化为 REST 服务