標籤:
什麼是 SOA?
如果公司有大量應用程式,這些程式供不同部門的承擔不同責任的職員使用,那麼就適合使用面向服務體繫結構(Service Oriented Architecture,SOA)。這些應用程式可以共用功能,但是功能的組合、使用者介面細節和易用性需求是不同的。與許多企業體繫結構一樣,SOA 也採用一個多層模型,但是它不只如此。在伺服器中,功能分散在單獨的服務上。一個客戶機可以使用其中的一個或多個服務,而一個服務也可以由許多客戶機使用。由此形成了一個鬆散耦合的體繫結構,這大大提高了現有軟體的可重用性。
常用的重型實現
常用縮寫詞
- API:API(Application program interface)
- IT:資訊技術(Information technology)
- XML:可延伸標記語言 (XML)(Extensible Markup Language)
SOA 尤其適合大公司,大公司往往有數百個應用程式,應用程式之間缺少良好的整合,所以公司需要清理 IT 基礎結構。SOA 是一種已經證明有效實踐,對於大型環境尤其有效。採用 SOA 的公司可以把遺留的應用程式轉換為服務,並把服務整合為現代應用程式的後端。可以使用中介軟體技術對服務進行組合,並對服務中的特定功能進行存取控制。因為在大型環境中對 SOA 的需求最為強烈,所以中介軟體技術的廠商通常把產品的重點放在大型和重型解決方案上。
SOA 和輕量型技術
SOA 背後的思想對於小公司同樣是有價值的。重型解決方案的設定成本和所需的人員技能可能使小公司不敢嘗試 SOA — 但這是不應該的。暫且不要考慮重型實現,我們先來考察一下 SOA 的基本概念:
- 從現有的或新的應用程式中提取出服務
- 把服務集中起來,供許多客戶機使用
並沒有什麼因素妨礙我們用輕量型技術實現這些思想。您可以先建立一個小型 SOA 並逐漸擴充它。如果您的公司以後發展成大型跨國公司,隨時可以遷移到重型技術。
回頁首
什麼是 REST?
SOA 通常是用 SOAP 協議實現的,服務由一個 WSDL(Web Services Description Language, Web 服務描述語言)文檔來描述。儘管有許多開發工具大大簡化了對 SOAP 和 WSDL 的處理過程,但是我仍然把它們看作重型技術,因為如果不使用這些工具,SOAP 和 WSDL 是很難處理的。
也可以通過超文字傳輸通訊協定 (HTTP)(HTTP)發送簡單的訊息來實現 SOA。這基本上就是 REST 式 Web 服務 (RESTful Web services) 的工作方式。Representational State Transfer(簡稱 REST,中文翻譯“具象狀態傳輸”。REST 這個名稱是由 Roy Fielding 首創的)並不是一個協議或技術;它是一種體繫結構風格。REST 是 SOAP 的輕量型替代品,它是面向資源的,而不是面向操作的。它常常被歸結為遠程過程使用 HTTP 調用 GET、POST、PUT 和 DELETE 語句。我認為,這隻是第二個重要的步驟。
第一個(也是最重要的)步驟是把所有資源建模為 URL 形式。URL 容易記憶,同時能夠訪問無數 Web 頁面。至少,如果建模方式適當的話,很容易記住 URL(比如 http://www.ibm.com/developerworks/xml/)。如果過分重視 GET、POST、PUT 和 DELETE,就可能產生不容易記憶的 URL,比如 http://www.longfakeurl.com/pol_srdm/70612/9,3993.32?id=78688&lang=cz&st=idx。
在實踐中,使用 HTTP 的方法可以進一步限制為 GET 和 POST 兩種方法,因為大多數瀏覽器對它們的支援很完善。可以對 http://domain.com/myresources/new 執行 POST,以替代對 http://domain.com/myresources 執行 PUT;對 http://domain.com/myresources/oldresource/delete 執行 POST,以替代對 http://domain.com/myresources/oldresource 執行DELETE。
REST 式的設計過程
在設計 REST 式 Web 服務時,可以採用以下四個步驟:
- 決定資源及其描述性 URL。
- 為每個 URL 上的通訊選擇一種資料格式。
- 指定每個資源上的方法。
- 指定返回的資料和狀態代碼。
以下是具體的設計過程。假設您是一家航空公司的開發人員。公司有用於預訂航班的軟體,還有處理付款(現金和信用卡)的組件。它使用軟體跟蹤包裹、執行內部資源規劃和執行許多其他任務。
假設機場登記處的職員使用一個客戶機應用程式,這個程式訪問包裹Tracing Service,還使用一個服務為乘客分配座位。處理包裹的地勤人員只需要包裹Tracing Service,不需要其他服務。他們的客戶機只允許他們確認已經登記的包裹是否到達了。不允許他們登記新的包裹。
在這個樣本中,我們將設計包裹Tracing Service。首先,決定資源:旅行者、航班和包裹(注意,在出現 {id} 的任何地方,都可以填寫任一數字):
http://luggagetracking.airlinecompany.com/bags/{id} http://luggagetracking.airlinecompany.com/flights/{id} http://luggagetracking.airlinecompany.com/travellers/{id} |
為每個資源選擇一種資料格式:
包裹:
<bag id="{id}"> <traveller id="{traveller-id}"/> <flight id="{flight-id}" /> <status>{current-status: departure/plane/arrival}</status> </bag> |
航班:
<flight id="{id}"> <travellers> <traveller id="{traveller-id-0}" /> <traveller id="{traveller-id-1}" /> <traveller id="{traveller-id-2}" /> </travellers> <bags> <bag id="{bag-id-0}" /> <bag id="{bag-id-1}" /> <bag id="{bag-id-2}" /> </bags> </flight> |
乘客:
<traveller id="{id}"> <flight id="{flight-id}" /> <bags> <bag id="{bag-id-0}" /> <bag id="{bag-id-1}" /> <bag id="{bag-id-2}" /> </bags> </traveller> |
顯然,這個模型過於簡單了。對於當前的樣本,只需要支援兩個方法,因此這個模型已經足夠了。登記處應該能夠為乘客登記新包裹。在把包裹裝進飛機時,地勤人員應該能夠修改包裹的狀態:
- 對 http://luggagetrackingairlinecompany.com/travellers/{id}/newbag 執行
POST,返回一個 <bag>XML 結構。
- 對 http://luggagetracking.airlinecompany.com/bags/{id}/status/{newstatus} 執行
POST,返回修改後的 XML 結構。
使用標準的 HTTP 狀態作為狀態代碼。成功的操作都會返回 200。如果系統無法根據資源的 ID 找到它,就會返回 404。系統故障導致的任何錯誤都會返回 500。
程式碼範例:URL 對應
可以使用多種方式把 URL 對應到實現方法。比較先進的方法可能更靈活,應該用在比較大的應用程式中。這個小樣本使用最簡單的方法:Regex。下面是 BagServlet 上的 post 方法樣本,它把 URL 參數傳遞給底層 servlet。可以在本文的下載檔案中找到完整的 servlet 代碼。注意,這裡沒有實現實際的底層服務。 以下是該樣本:
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Pattern pattern = Pattern.compile("^/?.*?/bags/(.*)/status/(.*)$"); Matcher matcher = pattern.matcher(request.getRequestURI()); if(matcher.matches()) { String bagId = matcher.group(1); String newStatus = matcher.group(2); bagService.changeBagStatus(bagId, newStatus); } } |
在調用這個 URL 時,如果成功,就會隱式地返回狀態代碼 200。更有意義的是,代碼返回 XML 結構。這個樣本使用 XStream API 把 Java™ 對象轉換成 XML 結構。這個 API 需要的配置非常少,而且主要根據類中的欄位名選擇元素名。
這個範例程式碼使用下面這些簡單的類:
航班:
package eu.adraandejonge.restfulsoa; public class Flight { String id; public Flight(String id) { super(); this.id = id; } } |
乘客:
package eu.adraandejonge.restfulsoa; public class Traveller { private String id; public Traveller(String id) { super(); this.id = id; } } |
包裹:
package eu.adraandejonge.restfulsoa; public class Bag { private String id; private Flight flight; private Traveller traveller; private String status; public Bag(String id, Flight flight, Traveller traveller, String status) { super(); this.id = id; this.flight = flight; this.traveller = traveller; this.status = status; } } |
假設底層的 BagService 返回一個包裹,包裹的航班 ID 是 1,乘客 ID 是 1,狀態是 new。請考慮下面的 GET 實現:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Pattern pattern = Pattern.compile("^/?.*?/bags/(.*)$"); Matcher matcher = pattern.matcher(request.getRequestURI()); if (matcher.matches()) { String bagId = matcher.group(1); Bag bag = bagService.retrieveBag(bagId); XStream xstream = new XStream(); xstream.alias("bag", Bag.class); xstream.alias("traveller", Traveller.class); xstream.alias("flight", Flight.class); xstream.useAttributeFor(Bag.class, "id"); xstream.useAttributeFor(Traveller.class, "id"); xstream.useAttributeFor(Flight.class, "id"); String xml = xstream.toXML(bag); response.getWriter().write(xml); } } |
在查詢這個 URL 時,它會返回以下資訊:
<bag id="1"> <flight id="1"/> <traveller id="1"/> <status>new</status> </bag> |
回頁首
還能做什嗎?
我選擇這些範例程式碼是為了說明,不需要很多底層通訊,URL 也能夠實現很多功能。對於其他服務,可能需要處理上傳給 REST 服務的 XML 結構。XStream 也可以協助完成這個任務。例如,要想對包裹的 XML 結構進行去序列化,應該調用:
Bag bag = (Bag) xstream.fromXML(xml); |
客戶機上的應用程式
到目前為止,本文已經討論了伺服器端的實現。用戶端上的代碼非常相似。客戶機可以共用資料類 Flight、Traveller 和 Bag,並使用 XStream API 對 XML 進行序列化和去序列化。客戶機上惟一的新部分是串連 URL 並讀取內容或發送內容。通過使用 Java 類庫提供的 URL 串連,很容易完成這個任務:
String xml = "<newinput>input</newinput>"; URL url = new URL("http://luggagetracking.airlinecompany.com/bags/1/newmethod"); URLConnection connection = url.openConnection(); // set POST connection.setDoOutput(true); Writer output = new OutputStreamWriter(connectiongetOutputStream()); output.write(xml); output.close(); // display result BufferedReader input = new BufferedReader( new InputStreamReader(connection.getInputStream())); String decodedString; while ((decodedString = input.readLine()) != null) { System.out.println(decodedString); } input.close(); |
與 Ruby on Rails 等技術的互通性
儘管 REST 並沒有明確的規範來規定如何?它,但是對 REST 的開箱即用支援越來越多了。因此,雖然沒有需要遵循的標準,但是您需要遵守一些約定。例如,Ruby on Rails 提供 ActiveResource。如果遵守 Rails 對 URL 和輸出格式的約定,就很容易用最小的開銷把 Rails Web 客戶機串連到 Java REST 式 Web 服務。
延展性和向重型 SOA 的遷移
隨著應用程式環境的增長,很可能會對越來越多的 REST 實現細節進行抽象。當增長和抽象發展到一定程度之後,從輕量型技術遷移到重型的 SOA 技術可能會節省成本。這需要把服務背後的實際商務邏輯提取出來,並重新封裝在新環境中的一個 SOAP 包中,這個過程應該不是太難。
尋找應用 REST 式 SOA 的機會
航空公司只是本文使用的一個樣本。實際的航空公司規模都比較大,它們應該直接使用重型技術。如果您為小公司工作,可能需要發揮想像力,尋找到在實踐中應用 SOA 和 REST 原則的最佳方式。花些時間考慮這個問題,這會帶來長遠的回報!
使用 XML 實現 REST 式的 SOA