脈絡:數個應用程式想用事件通知來協調彼此的動作,並希望用訊息傳遞來傳達這些事件。

為什麼 RPC 不適合送事件#

有時一個物件內發生的事件,另一個物件需要知道。經典例子是模型改變了狀態,必須通知它的視圖重繪自己。這類變更通知在分散式系統中同樣有用——例如在 B2B 系統中,一家企業可能需要通知其他企業價格變動或整份新的產品型錄。

行程可以用遠端程序呼叫來通知其他應用程式變更事件,但:

  • 要求接收端立刻接受該事件,即使它現在並不想要事件。
  • RPC 還要求發布事件的行程認識每一個監聽行程,並對每一個各發一次 RPC。

Observer 模式 [GoF] 描述了如何設計「發布事件的主體」與「消費事件的觀察者」:主體透過呼叫觀察者的 Update() 方法來通知它。Update() 可以用 RPC 實作,但那樣就會帶上 RPC 的全部缺點。

更好的作法是非同步地把事件通知當成一則 Message 送出:主體在自己準備好時送出通知,每個觀察者則在自己準備好時(且如果它想要的話)接收通知。

解法#

主體有事件要宣告時,會建立事件物件、把它包進訊息、送上通道;觀察者收到事件訊息、取出事件並處理它。

Event Message 可以是訊息系統中的任何一種訊息。在 Java 中,事件可以是物件或 XML 文件之類的資料,因此能以 ObjectMessageTextMessage 等形式透過 JMS 傳輸;在 .NET 中,它就是一則裝著事件的 Message

圖 5-1:Event Message 解法示意

與文件訊息的差別:時機與內容#

  • 事件的內容通常比較不重要——許多事件甚至是空的,它「發生了」這件事本身就足以叫觀察者做出反應
  • 事件的時機非常重要——主體應在變化一發生就發出事件,觀察者也應在事件仍然相關時盡快處理它。

因此:

  • Guaranteed Delivery 對事件通常幫助不大——事件頻繁,而且需要快速送達。
  • Message Expiration 則非常有用——確保事件要嘛被快速處理,要嘛乾脆不處理。

推模型 vs. 拉模型#

前面的 B2B 例子可以用事件訊息、文件訊息,或兩者的組合:

  • 「電腦硬碟的價格變了」——這是事件
  • 「這是這顆硬碟的資訊,包含新價格」——這是以事件形式送出的文件
  • 「有新型錄了,網址在這裡」——這是事件;而一則實際裝著新型錄的類似訊息,則是含有文件的事件

哪個比較好?Observer 模式把這描述成推模型與拉模型之間的取捨

推模型#

訊息是文件/事件的合體:訊息的送達宣告狀態已改變,訊息的內容就是新狀態

若所有觀察者都想要這些細節,推模型更有效率。

否則它可能是兩者的最壞組合:一則又大又頻繁、而且經常被許多觀察者忽略的訊息

拉模型#

拉模型有三則訊息:

  • Update——一則 Event Message,通知觀察者事件發生
  • State Request——一則 Command Message,由有興趣的觀察者用來向主體索取細節
  • State Reply——一則 Document Message,主體用它把細節送給觀察者

缺點:三則訊息取代一則,所需的通道與造成的流量都更多。

搭配#

  • 通常沒有理由用 Point-to-Point Channel 把事件訊息限制給單一接收者;它一般透過 Publish-Subscribe Channel 廣播,好讓所有有興趣的行程都收到通知。
  • Document Message 必須被消費,否則文件就遺失了;而 Event Message 的接收端在忙不過來時往往可以直接忽略訊息,因此訂閱者常常可以是非持久的(不必是 Durable Subscriber)。