程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> 智能客戶端-使用 NHibernate 和 Rhino 服務總線構建分布式應用程序

智能客戶端-使用 NHibernate 和 Rhino 服務總線構建分布式應用程序

編輯:關於.NET

有很長一段時間,我的工作內容幾乎都是 Web 應用程序。當我要構建一個智能客戶端應用 程序時,起初我覺得非常困惑,不知該如何構建這樣的應用程序。怎麼處理數據訪問?智能客 戶端應用程序與服務器之間如何通信?

而且,我那時已經投入很多,擁有一些能夠顯著減少開發時間和成本的工具,而我真的希望 可以繼續使用這些工具。我花了一段時間來深入考慮各種細節問題,在這期間,我一直在想如 何讓 Web 應用程序更簡單些呢,當然我需要先知道如何處理這樣的應用程序。

智能客戶端應用程序有利有弊。從有利的一面看,智能客戶端反應靈敏,能夠改進與用戶的 交互性。如果您將處理移到客戶端計算機,還能減輕服務器的負載,而且即使與後端系統斷開 連接,用戶也能照常工作。

另一方面,智能客戶端又有一些固有的問題,包括通過 Intranet 或 Internet 訪問數據時 需要解決速度、安全性和帶寬限制等問題。您還要負責在前端與後端系統之間實現數據同步、 處理分布式的更改跟蹤,以及處理在偶爾連接的環境中使用時出現的問題。

本文中討論的智能客戶端應用程序可以使用 Windows Presentation Foundation (WPF) 或 Silverlight 構建。由於 Silverlight 具備一部分 WPF 功能,因此我在此處介紹的技巧和方 法同時適用於這兩種工具。

在本文中,我將使用 NHibernate 進行數據訪問,使用 Rhino 服務總線實現與服務器之間 的可靠通信,從而開始規劃和構建智能客戶端應用程序的過程。我要構建的應用程序名為 Alexandra,是一個在線借閱圖書館的前端系統。該應用程序本身分成兩大部分。第一部分是運 行了一組服務的應用程序服務器(大部分的業務邏輯都在此服務器上),使用 NHibernate 訪 問數據庫。第二部分是智能客戶端 UI,讓用戶輕松使用上述那些服務。

NHibernate 是一個對象關系映射 (O/RM) 框架,旨在讓用戶輕松使用關系數據庫,就像使 用內存中的數據一樣。Rhino 服務總線是構建在 Microsoft .NET Framework 上的開源服務總 線,其主要目的是讓部署、開發和使用變得輕松自如。

職責的分配

構建借閱圖書館的第一步是正確分配前端系統和後端系統各自的職責。一種途徑是讓應用程 序主要負責 UI,以便大部分的處理都在客戶端計算機上進行。在這種情況下,後端服務器幾乎 就是一個數據存儲庫。

從本質上說,這就是傳統的客戶端/服務器應用程序,後端系統只是作為數據存儲的代理。 如果後端系統只是一個數據存儲庫,這不失為一種有效的設計選擇。例如,這種體系結構可能 很適合個人圖書目錄,因為這種應用程序的作用就是為用戶管理數據,服務器端不需要對數據 進行操作。

對於這樣的應用程序,我建議您使用 WCF RIA 服務或 WCF 數據服務。如果您希望後端服務 器向外界提供一個 CRUD 接口,則使用 WCF RIA 服務或 WCF 數據服務能讓您顯著減少構建應 用程序所需的時間。盡管這兩種技術都允許您將自己的業務邏輯添加到 CRUD 接口中,但是任 何通過添加邏輯來實現重要應用程序功能的嘗試最終都可能導致不可收拾的混亂局面。

我在本文中不會探討如何構建這樣的應用程序,不過 Brad Adams 在他的博客 blogs.msdn.com/brada/archive/2009/08/06/business-apps-example-for-silverlight-3- rtm-and-net-ria-services-july-update-part-nhibernate.aspx 中詳細介紹了使用 NHibernate 和 WCF RIA 服務構建這種應用程序的操作步驟。

與上述完全不同的方法是,您可以選擇在後端實現應用程序的大部分功能,而前端只是負責 顯示。這種方法初看起來似乎是合理的,因為通常您就是這樣編寫基於 Web 的應用程序的,但 這也意味著您不能在客戶端運行真正的應用程序。狀態管理將會變得更困難。從本質上說,您 仍是在編寫 Web 應用程序,其所有復雜問題都不可避免。您將無法把處理轉移到客戶端計算機 ,也不能處理連接中斷的問題。

更糟的是,從用戶角度而言,這種方法意味著您展示的 UI 很遲鈍,因為所有操作都需要先 傳遞到服務器再返回。

我敢肯定,我在本例中采用的方法居於上述二者之間,這件事不會讓您感到驚訝。我要在客 戶端計算機上運行以充分利用由此帶來的各種機會,但同時,應用程序的大部分功能將作為服 務在後端運行,如圖 1 所示。

圖 1 應用程序的體系結構

該示例解決方案包括三個項目,您可以從 github.com/ayende/alexandria 下載。 Alexandria.Backend 是包含後端代碼的控制台應用程序。Alexandria.Client 包含前端代碼, Alexandria.Messages 包含在前兩者之間共享的消息定義。若要運行該示例, Alexandria.Backend 和 Alexandria.Client 都需要運行。

將後端托管在控制台應用程序中的一個好處就是,您可以方便地模擬連接斷開時的情景:只 需先關閉後端控制台應用程序,之後再重新打開。

有關分布式計算的謬論

有了體系結構的基本框架,我們來看看編寫智能客戶端應用程序的含義。與後端的通信將通 過 Intranet 或 Internet 進行。考慮到大多數 Web 應用程序中的遠程調用主要來自位於同一 數據中心(通常就在同一機架中)的數據庫或另一個應用程序服務器,因此這將嚴重影響好幾 個含義。

Intranet 和 Internet 連接會受到速度問題、帶寬限制和安全性的制約。如果應用程序的 所有重要組成部分都在同一個數據中心內,通信成本的巨大差距將迫使您選擇與現行通信結構 完全不同的結構。

在分布式應用程序中,您需要處理的最大障礙就是有關分布式計算的謬論。開發人員在構建 分布式應用程序時往往會提出若干個假設,而最終這些假設都不成立。依靠這些根本不成立的 假設(謬論)通常會導致功能減少,或者導致成本非常高的系統重新設計和重新構建。以下就 是八條謬論:

網絡穩定可靠。

沒有絲毫延遲。

帶寬是無限的。

網絡是安全的。

拓撲不會改變。

有一位管理員。

傳輸成本是零。

網絡是同構的。

不考慮這些謬論的分布式應用程序都將遇到服務器問題。因此智能客戶端應用程序需要解決 這些問題。在這種情況下,使用緩存就成為一個重要手段。即使您不打算讓應用程序在連接斷 開時仍能運行,緩存對於提升應用程序的響應性也是很有用的。

另一個需要考慮的事項就是應用程序的通信模型。可能最簡單的模型就是標准的服務代理, 該代理允許您執行遠程過程調用 (RPC),但這樣做可能會在將來造成問題。它需要使用更復雜 的代碼來處理連接斷開的情況,如果您要避免阻塞 UI 線程,則還要顯式處理異步調用。

後端基礎知識

接下來的問題就是如何采用合適的方法來構造應用程序後端,以便既能獲得良好的性能,又 能與 UI 的結構有適當的分離。

從性能和響應性的角度而言,理想的方案是只需對後端執行一次調用,就能為正在顯示的屏 幕獲得所有必要的數據。這樣做的問題就是您需要一個能夠精確模擬智能客戶端 UI 的服務接 口。出於許多原因,這不是明智的做法。最主要的原因就是 UI 是應用程序中最易變的部分。 以這種方式將服務接口綁定到 UI 會導致頻繁更改服務,而這些更改僅僅是因為 UI 發生了變 化。

而頻繁更改服務又會導致應用程序的部署變得更困難。您必須同時部署前端和後端,而嘗試 在同一時刻支持多個版本可能會導致情況更加復雜。此外,該服務接口無法用來構建其他 UI 或者作為第三方或其他服務的集成點。

如果您嘗試使用其他方法(即構建標准的、精細的接口),您就會遇到謬論問題(精細的接 口會產生大量遠程調用,從而導致延遲、可靠性和帶寬問題)。

解決這個問題的方案就是脫離常見的 RPC 模型。我們不提供可以遠程調用的方法,而是使 用本地緩存和面向消息的通信模型。

圖 2 顯示了如何從前端向後端打包發送幾個請求。這樣您就可以進行一次遠程調用,但在 服務器端保留一個與 UI 需求的聯系並不緊密的編程模型。

圖 2 發送給服務器的一個請求包含多條消息

要增強響應性,您可以加入一個可以立即應答某些查詢的本地緩存,從而獲得反應更靈敏的 應用程序。

在這些方案中,您要考慮的事情之一就是您有什麼類型的數據,以及要顯示的數據的刷新要 求。在 Alexandria 應用程序中,我主要依靠本地緩存,因為本地緩存在應用程序向後端系統 請求最新數據期間,可以為用戶顯示緩存的數據。而其他應用程序(例如股票交易程序)可能 應該不顯示任何數據(不能顯示陳舊的數據)。

連接斷開時的操作

您面臨的下一個問題是處理連接斷開的情況。在很多應用程序中,您可以指定連接是強制的 ,這意味著當後端服務器不可訪問時,您只需為用戶顯示一個錯誤。但智能客戶端應用程序的 優勢之一就是它可以 在連接斷開時繼續運行,Alexandria 應用程序就充分利用了這一點。

但是,這意味著緩存變得更加重要,因為既要使用它來提高通信速度,還要在後端系統不可 訪問時利用它來提供數據。

到目前為止,我相信您已經很好地了解了構建這樣的應用程序所面臨的問題,現在讓我們看 看如何解決這些問題。

隊列是我的最愛之一

在 Alexandria 中,前端和後端之間不存在 RPC 通信。相反,正如圖 3 所示,所有的通信 都通過排入隊列的單向消息進行處理。

圖 3 Alexandria 的通信模型

隊列為解決前述的通信問題提供了一種相當不錯的解決方式。前端和後端之間無需直接通信 (直接通信意味著很難 支持連接斷開的情況),您可以讓隊列子系統處理所有通信。

使用隊列非常簡單。您讓本地的隊列子系統向某個隊列發送消息。隊列子系統擁有消息的所 有權,並確保消息在某一時刻到達其目標。但是您的應用程序並不需要等待消息到達其目標, 而可以繼續其工作。

如果目標隊列目前不可用,則隊列子系統會等待直到目標隊列變為可用,才會發送消息。隊 列子系統通常會將消息保存在磁盤上直到可以發送消息,因此即使源計算機已經重新啟動,待 定的消息還是能夠到達其目標。

使用隊列時,可以輕松地構思消息和目標。到達後端系統的消息將觸發某種操作,從而導致 回復被發送給原始消息發送方。請注意,無論哪一方都不會被阻塞,因為每個系統都是完全獨 立的。

隊列子系統包括 MSMQ、ActiveMQ、RabbitMQ 等等。Alexandria 應用程序使用了 Rhino Queues (github.com/rhino-queues/rhino-queues),這是一個通過 xcopy 部署的開源隊列子 系統。我選擇 Rhino Queues 的原因很簡單,它不需要安裝或管理工作,因此非常適合用在示 例中以及用在需要部署到許多計算機的應用程序中。還要請您注意的是,Rhino Queues 的作者 是我,希望您會喜歡它。

開始使用隊列

讓我們看看您如何使用隊列為主屏幕獲取數據。以下是 ApplicationModel 初始化例程:

protected override void OnInitialize() {
  bus.Send(
   new MyBooksQuery { UserId = userId },
   new MyQueueQuery { UserId = userId },
   new MyRecommendationsQuery { UserId = userId },
   new SubscriptionDetailsQuery { UserId = userId });
}

我向服務器發送一批消息,請求若干信息。此處需要注意幾個事項。發送消息的頻率很高。 我並沒有發送一條通用的消息(例如 MainWindowQuery)而是發送了很多消息(MyBooksQuery 、MyQueueQuery 等等),每一條消息都是為了獲得一條具體的信息。正如前文所論述的,這樣 做既可以一次性發送多條消息以減少網絡往返,還可以降低前端和後端的聯系緊密度。

RPC 是您的敵人

構建分布式應用程序時最常見的錯誤之一就是忽略應用程序的分布性。以 WCF 為例,就很 容易使人忽略需要通過網絡進行方法調用。盡管 WCF 是非常簡單的編程模型,但您需要特別小 心,不要違反有關分布式計算的某個謬論。

事實上,正是由於像 WCF 這樣的框架提供的編程模型非常像您在本地計算機上調用方法時 使用的模型,才會讓您做出那些錯誤的假設。

如果使用標准的 RPC API,則意味著當通過網絡進行調用時會出現阻塞、遠程方法調用的成 本會更高以及如果後端服務器不可用則可能失敗。雖然在此基礎上完全有可能構建一個優秀的 分布式應用程序,但需要格外小心謹慎。

如果采用別的方法,您將會用到基於顯式消息交換的編程模型(即與大多數基於 SOAP 的 RPC 堆棧中常用的隱式消息交換相反)。這樣的模型初看起來可能很奇怪,其實您只需要稍微 調整一下思路,就會發現您要擔心的復雜問題會大大減少。

我的示例應用程序 Alexandria 就構建在單向消息傳送平台上,並充分地利用了這個平台, 因此我的應用程序知道自己是分布式的,並真正了利用了這一點。

請注意,所有的消息都以 Query 這個詞結尾。我用這樣的命名約定來標識純粹的查詢消息 ,這種消息不會改變狀態,但需要某種類型的響應。

最後還需要注意的是,看起來我並未從服務器獲得任何回復。因為我使用了隊列,因此通信 模式是發出消息後就不加處理。我現在發出一條消息(或一批消息),要在後面的階段才處理 回復。

在介紹前端如何處理響應之前,我們先看看後端如何處理我剛才發送的消息。圖 4 顯示了 後端服務器如何使用圖書查詢。您將在這裡第一次看到我如何結合使用 NHibernate 和 Rhino 服務總線。

圖 4 在後端系統上使用查詢

public class MyBooksQueryConsumer :
  ConsumerOf<MyBooksQuery> {

  private readonly ISession session;
  private readonly IServiceBus bus;

  public MyBooksQueryConsumer(
   ISession session, IServiceBus bus) {

   this.session = session;
   this.bus = bus;
  }

  public void Consume(MyBooksQuery message) {
   var user = session.Get<User>(message.UserId);

   Console.WriteLine("{0}'s has {1} books at home",
    user.Name, user.CurrentlyReading.Count);

   bus.Reply(new MyBooksResponse {
    UserId = message.UserId,
    Timestamp = DateTime.Now,
    Books = user.CurrentlyReading.ToBookDtoArray()
   });
  }
}

在深入研究用於處理此消息的實際代碼之前,讓我們先討論一下此代碼的運行結構。

消息綜述

毫無疑問,Rhino 服務總線 (hibernatingrhinos.com/open-source/rhino-service-bus) 是一種服務總線。它是基於單向隊列消息交換機制的通信框架,開發靈感主要來自於 NServiceBus (nservicebus.com)。

發送到總線的消息將到達其目標隊列,在目標隊列處將調用消息的使用者。圖 4 中的消息 使用者是 MyBooksQueryConsumer。消息使用者是實現 ConsumerOf<TMsg> 的類,並且使 用相應的消息實例調用 Consume 方法以處理該消息。

您可能根據 MyBooksQueryConsumer 構造函數猜測我使用控制反轉 (IoC) 容器來提供消息 使用者的依賴關系。對於 MyBooksQueryConsumer,那些依賴關系就是總線本身以及 NHibernate 會話。

使用消息的實際代碼非常簡單。您從 NHibernate 會話獲得相應的用戶,然後使用請求的數 據回復消息發出者。

前端也有消息使用者。該使用者針對的是 MyBooksResponse:

public class MyBooksResponseConsumer :
  ConsumerOf<MyBooksResponse> {

  private readonly ApplicationModel applicationModel;

  public MyBooksResponseConsumer(
   ApplicationModel applicationModel) {
   this.applicationModel = applicationModel;
  }

  public void Consume(MyBooksResponse message) {
   applicationModel.MyBooks.UpdateFrom(message.Books);
  }
}

這只是利用來自消息的數據更新應用程序模型。但有一件事需要注意:使用者不 是在 UI 線程上調用的,而是在後台線程上調用的。但應用程序模型已綁定到 UI,因此更新模型必須 在 UI 線程上進行。UpdateFrom 方法知道這一點,因此它將切換到 UI 線程上,以便在正確的 線程中更新應用程序模型。

在後端和前端,處理其他消息的代碼都是相似的。這種通信是純粹的異步通信。您在任何時 候都不需要等待來自後端的回復,也無需使用 .NET Framework 的異步 API。相反,您要使用 顯式的消息交換,這種交換通常是幾乎立即發生的,但如果您處於連接斷開模式,也可以延遲 一段時間。

早先當我向後端發送查詢時,我只是告訴總線發送消息,但並沒有說要發送到哪裡。在圖 4 中,我調用了 Reply,還是沒有指定應該將消息發送到哪裡。那麼總線是如何知道要向哪裡發 送這些消息的呢?

如果是向後端發送消息,那麼這個問題的答案就是:配置。在 App.config 中,您會發現以 下配置:

<messages>
  <add name="Alexandria.Messages"
   endpoint="rhino.queues://localhost:51231/alexandria_backend"/>
</messages>

這告訴總線所有命名空間以 Alexandria.Messages 開頭的消息都應該發送到 alexandria_backend 端點。

在後端系統處理消息時,調用 Reply 表示將消息發回到其發送者。

此配置指定了消息的所有權,即消息放置到總線上後應該發送給誰,以及將訂閱請求發送到 哪裡,才能在發布此類型的消息時將您加入到分發列表中。我在 Alexandria 應用程序中沒有 使用消息發布,因此本文不會涉及此話題。

會話管理

現在您已經了解通信機制如何運作,但是在繼續之前還需要了解一些有關基礎結構的事項。 因為在所有 NHibernate 應用程序中,您都需要找到管理會話生命周期並正確處理事務的方法 。

Web 應用程序的標准方法是為每個請求創建一個會話,使每個請求都有自己的會話。對於消 息傳送應用程序,做法幾乎是一樣的。不一樣的地方在於不是每個請求一個會話,而是每批消 息一個會話。

事實證明,這件事幾乎完全是由基礎結構處理的。圖 5 顯示了後端系統的初始化代碼。

圖 5 初始化消息傳送會話

public class AlexandriaBootStrapper :
  AbstractBootStrapper {

  public AlexandriaBootStrapper() {
   NHibernateProfiler.Initialize();
  }

  protected override void ConfigureContainer() {
   var cfg = new Configuration()
    .Configure("nhibernate.config");
   var sessionFactory = cfg.BuildSessionFactory();

   container.Kernel.AddFacility(
    "factory", new FactorySupportFacility());

   container.Register(
    Component.For<ISessionFactory>()
     .Instance(sessionFactory),
    Component.For<IMessageModule>()
     .ImplementedBy<NHibernateMessageModule>(),
    Component.For<ISession>()
     .UsingFactoryMethod(() =>
      NHibernateMessageModule.CurrentSession)
     .LifeStyle.Is(LifestyleType.Transient));

   base.ConfigureContainer();
  }
}

引導是 Rhino 服務總線中的顯式概念,由從 AbstractBootStrapper 派生的類實現。引導 程序執行的功能與一般 Web 應用程序中的 Global.asax 相同。在圖 5 中,我首先構建了 NHibernate 會話工廠,然後設置容器 (Castle Windsor) 以便從 NHibenrateMessageModule 提供 NHibernate 會話。

消息模塊與 Web 應用程序中的 HTTP 模塊具有相同的目的:處理所有請求的共同事項。我 使用 NHibernateMessageModule 管理會話生存期,如圖 6 所示。

圖 6 管理會話生存期

public class NHibernateMessageModule : IMessageModule {
  private readonly ISessionFactory sessionFactory;
  [ThreadStatic] 
  private static ISession currentSession;

  public static ISession CurrentSession {
   get { return currentSession; }
  }

  public NHibernateMessageModule(
   ISessionFactory sessionFactory) {

   this.sessionFactory = sessionFactory;
  }

  public void Init(ITransport transport,
   IServiceBus serviceBus) {

   transport.MessageArrived += TransportOnMessageArrived;
   transport.MessageProcessingCompleted
    += TransportOnMessageProcessingCompleted;
  }

  private static void
   TransportOnMessageProcessingCompleted(
   CurrentMessageInformation currentMessageInformation,
   Exception exception) {

   if (currentSession != null)
     currentSession.Dispose();
   currentSession = null;
  }

  private bool TransportOnMessageArrived(
   CurrentMessageInformation currentMessageInformation) {

   if (currentSession == null)
     currentSession = sessionFactory.OpenSession();
   return false;
  }
}

這段代碼相當簡單:注冊相應的事件、在合適的位置創建和釋放會話,就這麼簡單。

這種方法的一個有趣之處在於一個批次中的所有消息將共享同一個會話,這意味著在很多情 況下,您可以利用 NHibernate 的一級緩存。

事務管理

會話管理就是這樣了,那麼事務管理又是怎樣的呢?

一項有關 NHibernate 的最佳實踐就是與數據庫進行的所有交互都應該通過事務進行處理。 但我在這裡並沒有使用 NHibernate 的事務。為什麼呢?

因為事務是由 Rhino 服務總線處理的。Rhino 服務總線沒有讓每個使用者管理自己的事務 ,而是采取了另一種方式。即,使用 System.Transactions.TransactionScope 創建一個事務 ,該事務包含該批次中消息的所有使用者。

這意味著在響應消息批次(而不是一條消息)時采取的所有操作都是同一事務的組成部分。 NHibernate 將自動在環境事務中登記一個會話,當您使用 Rhino 服務總線時,您就不必再顯 式處理事務了。

單個會話和單個事務的組合使得將多個操作組合到單個事務單元中變得極其輕松。同時也意 味著您可以直接得益於 NHibernate 的一級緩存。例如,下面是用於處理 MyQueueQuery 的相 關代碼:

public void Consume(MyQueueQuery message) {
  var user = session.Get<User>(message.UserId);

  Console.WriteLine("{0}'s has {1} books queued for reading",
   user.Name, user.Queue.Count);

  bus.Reply(new MyQueueResponse {
   UserId = message.UserId,
   Timestamp = DateTime.Now,
   Queue = user.Queue.ToBookDtoArray()
  });
}

處理 MyQueueQuery 和 MyBooksQuery 的實際代碼幾乎完全相同。那麼,以下代碼在性能方 面對於每個會話一個事務有何意義呢?

bus.Send(
  new MyBooksQuery {
   UserId = userId
  },
  new MyQueueQuery {
   UserId = userId
  });

初看起來,這段代碼要使用四個查詢來收集所有必需的信息。在 MyBookQuery 中,一個查 詢用於獲得相應的用戶,另一個查詢用於加載該用戶的圖書。在 MyQueueQuery 中看起來也是 一樣的:一個查詢用於獲取用戶,另一個查詢用於加載該用戶的隊列。

但是為整個批次使用一個會話表明您實際上在使用一級緩存,以避免不必要的查詢,正如圖 7 中 NHibernate Profiler (nhprof.com) 的輸出所示。

圖 7 處理請求的 NHibnerate Profiler 視圖

支持偶爾連接的方案

實際上,應用程序在無法連接後端服務器時不會引發錯誤,但這樣不是非常好用。

該應用程序進化的下一步是引入緩存,使應用程序在後端服務器不響應的情況下也能繼續運 行,從而將其變為真正的偶爾連接的客戶端。但我不會使用傳統的緩存體系結構,因為在這種 體系結構中,應用程序代碼會顯式調用緩存。相反,我將在基礎結構級別應用緩存。

圖 8 顯示了如果緩存作為消息傳送基礎結構的一部分,當您發送一條消息來請求有關用戶 圖書的數據時,各項操作的順序。

圖 8 在並發消息傳送操作中使用緩存

客戶端發送一條 MyBooksQuery 消息。該消息被發送到總線,與此同時,查詢緩存以查看其 中是否有對該請求的響應。如果緩存包含對前一個請求的響應,則緩存立即讓緩存的消息被使 用,就像該消息是剛到達總線一樣。

來自後端系統的響應到達。該消息正常地被使用,也放在緩存中。此方法表面上看似復雜, 但卻能帶來有效的緩存行為,而且能讓您在應用程序代碼中幾乎完全忽略緩存事項。使用永久 性緩存(即使應用程序重新啟動也存在的緩存),您無需從後端服務器獲取任何數據,即可完 全獨立地操作應用程序。

現在讓我們來實現此功能。我使用永久性緩存(示例代碼提供了簡單的實現方法,即,使用 二進制序列將值保存到磁盤)並定義了以下約定:

如果消息是請求/響應消息交換的一部分,則可以緩存該消息。

請求和響應消息都附帶用於消息交換的緩存鍵。

消息交換由 ICacheableQuery 接口定義,該接口具有一個 Key 屬性和一個 ICacheableResponse 接口,而 ICacheableResponse 接口具有 Key 和 Timestamp 屬性。

為了實現此約定,我編寫要在前端運行的 CachingMessageModule,用於截取傳入和傳出的 消息。圖 9 顯示了如何處理傳入的消息。

圖 9 緩存傳入的連接

private bool TransportOnMessageArrived(
  CurrentMessageInformation 
  currentMessageInformation) {

  var cachableResponse =
   currentMessageInformation.Message as
   ICacheableResponse;
  if (cachableResponse == null)
   return false;

  var alreadyInCache = cache.Get(cachableResponse.Key);
  if (alreadyInCache == null || 
   alreadyInCache.Timestamp <
   cachableResponse.Timestamp) {

   cache.Put(cachableResponse.Key,
    cachableResponse.Timestamp, cachableResponse);
  }
  return false;
}

到這裡就沒有太多內容了 - 如果消息是可緩存的響應,我就將其放到緩存中。但還有一件 事需要注意:我對無序的消息(即具有較早時間戳的消息比具有較晚時間戳的消息晚到達)進 行了處理。這可以確保只將最新的信息保存到緩存中。

您可以從圖 10 看到,處理傳出的消息和分派緩存中的消息更加有趣。

圖 10 分派消息

private void TransportOnMessageSent(
  CurrentMessageInformation 
  currentMessageInformation) {

  var cacheableQuerys =
   currentMessageInformation.AllMessages.OfType<
   ICacheableQuery>();
  var responses =
   from msg in cacheableQuerys
   let response = cache.Get(msg.Key)
   where response != null 
   select response.Value;

  var array = responses.ToArray();
  if (array.Length == 0)
   return;
  bus.ConsumeMessages(array);
}

我從緩存中收集了緩存的響應,並對它們調用 ConsumeMessages。這導致總線調用通常的消 息調用邏輯,因此看起來很像消息又一次到達。

但請注意,即使存在緩存的響應,您仍要發送消息。原因在於這樣您就可以向用戶提供快速 (緩存的)響應,並在後端回復新消息時更新向用戶顯示的信息。

後續步驟

我已經介紹了智能客戶端應用程序的基本構造塊:如何構造後端,以及如何構造智能客戶端 應用程序和後端之間的通信模式。後者很重要,因為選錯通信模式可能會導致分布式計算的各 種謬論。我還涉及了批處理和緩存,它們是提升智能客戶端應用程序性能的兩種重要方法。

您已經了解如何在後端管理事務和 NHibernate 會話,如何使用和回復來自客戶端的消息, 以及所有內容如何融入引導程序。

在本文中,我的主要關注點在基礎結構方面;下一期的文章將介紹在後端和智能客戶端應用 程序之間傳送數據的最佳實踐,以及分布式更改管理的模式。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved