脈絡:應用程式有一項服務想開放給其他應用程式使用。
為什麼不想被迫二選一#
應用程式未必想決定某項服務(Service Layer [EAA] 中的一個操作)該同步還是非同步被喚起,它可能想對同一項服務同時支援兩種作法。
而且:
設計要與其他應用程式協作的應用程式(例如 B2B 應用)時,開發者可能不知道自己要與哪些應用程式溝通、通訊又會如何進行。訊息技術與資料格式太多了,不可能為了以防萬一就全部支援。
接收與處理一則訊息牽涉數個步驟,要把這些步驟分開既困難又不必要地複雜;然而,把「接收訊息、取出內容、依內容執行工作」混在一起的 Message Endpoint 程式碼,會很難重用。
設計支援多種通訊風格的客戶端時,很容易覺得必須為每種風格各自重新實作那項服務——這讓支援每種新風格都很麻煩,還帶來「各風格的行為未必完全一致」的風險。
我們需要的,是讓單一項服務支援多種通訊風格的方式。
解法#
- Service Activator 可以是**單向(僅請求)或雙向(Request-Reply)**的
- 服務可以簡單到只是一次方法呼叫——同步且非遠端,可能是 Service Layer [EAA] 的一部分
- 啟動器可以寫死成永遠喚起同一項服務,也可以用反射喚起訊息所指明的服務
運作流程#
- Service Activator 接收請求訊息(可以是 Polling Consumer,也可以是 Event-Driven Consumer)
- 它知道訊息的格式,處理訊息以取出「該喚起哪項服務」與「該傳入哪些參數值」所需的資訊
- 像服務的任何其他客戶端一樣喚起服務,並在服務執行期間阻塞
- 服務完成並回傳值時,啟動器可選擇性地建立一則含有該值的回覆訊息並回傳給請求方(此時這次服務喚起就成了 Request-Reply 訊息的實例)
錯誤處理#
- 若 Service Activator 無法成功處理訊息,那則訊息就是無效的,應該被移到 Invalid Message Channel
- 若訊息能被處理、服務也被成功喚起,那麼執行服務過程中發生的任何錯誤都是應用程式的語意錯誤,應由應用程式處理
開發者仍然無法預測夥伴可能想用什麼方式存取自己的服務,但他們至少知道自己的應用程式會提供哪些服務、並能把它們實作出來——之後依需要為不同技術與格式實作新的啟動器就相對容易了。
延伸:與相關模式的關係
Service Activator 模式也被記載於 [CoreJ2EE](模式名最初出於該書)。
那個版本與本書版本略有不同——它假設啟動器是 Event-Driven Consumer,也假設服務已經存在、啟動器是被加到服務上的——但兩個版本以非常相似的方式,為同一個問題提出了相同的解法。
Service Activator 也與 Half-Sync/Half-Async 模式 [POSA2] 相關,後者把服務處理分成同步與非同步兩層。
搭配關係:
- Service Activator 通常接收 Command Message,因為它描述了該喚起哪項服務
- Service Activator 本身扮演 Messaging Gateway,把訊息細節與服務分離
- 啟動器可以是 Polling Consumer 或 Event-Driven Consumer
- 若服務具交易性,啟動器就該是 Transactional Client,讓訊息消費能與服務喚起參與同一個交易
- 多個啟動器可以是 Competing Consumers,或由 Message Dispatcher 協調
- 無法成功處理訊息時,應把訊息送往 Invalid Message Channel
範例:J2EE Enterprise JavaBeans
以 J2EE 中的 EJB 為例:
- 把服務封裝成 session bean
- 為各種訊息情境實作 message-driven bean——一個對應「使用某種格式訊息的 JMS 目的地」、另一個對應「使用另一種格式的另一個目的地」、再一個對應 web service/SOAP 訊息,依此類推
而想同步喚起服務的客戶端,可以直接存取那個 session bean。 >