天天看點

使用 ServiceAnt 更好地解耦你的程式

今天要厚着臉皮給大家推薦一個自己做的通信中間件——ServiceAnt,目前已經在我們團隊的兩個産品線上投入了使用。

ServiceAnt是什麼

它最初的定位是ESB(企業服務總線),但目前還沒有達到這個高度,主要是還是沒有提供分布式的實作,有機會會補上。

現在它隻能工作于程序内,與 Mediator 的角色非常類似。

可能有同學不知道 Mediator, Automapper 總該聽過吧?它們的作者是一個人。

ServiceAnt 部分的設計也參考了 Mediator,當然還有别的一些架構,比如 Abp 中的 EventBus, eShopOnContainer 的 integation event 以及 NServiceBus。

可能有人會問,通信中間件的作用是什麼?這裡我們先用現在火熱的微服務場景來舉個例子,假設我們擁有ABCDEF六個微服務,A需要與DE服務通信來獲得某些資訊,而B則是與EF,C與DF,D與AB,E與BC,F與AC。

那麼它們之間的通信拓撲圖将會是如下的樣子:

使用 ServiceAnt 更好地解耦你的程式

看上去相當地混亂,對吧?

在真實的網際網路應用中,拆分的服務數量和關聯度多數都要比上面這張圖更加複雜。

如果采用RESTful API 的方式來通信,這會造成每個服務都需要管理不同的多方連接配接資訊,給開發帶來了相當大的複雜度。

為了解決這樣的問題,我們在其間引入了一個中介者的角色來負責分發請求,傳遞結果,這就是通信中間件。

引入之後的拓撲圖如下:

使用 ServiceAnt 更好地解耦你的程式

無論你需要與多少方通信,最終你隻需要告訴中間件:目标位址、通信内容以及通信模式(Pub Or Request,如果支援的話)。

而不需要關心是誰,以什麼方式來處理通信内容,中間件會幫你處理好這些事情。

這樣一來我們獲得以下的優點:

  1. 解除了服務間的直接耦合,提高了擴充性
  2. 降低了開發的複雜度,避免管理通信相關的内容,如通信協定,安全性,以及監控等。

有同學忍不住要說了:你說的都是微服務啊,這些我都懂,比如SpringCloud就是這樣的,你剛剛說過 ServiceAnt 還沒有分布式的實作,那介紹微服務有錘子用啊?

别急,聽我慢慢解釋。我們知道在大型企業應用中都會把程式拆分為多個子產品對吧?

把以上兩圖的ABCDEF視作子產品,子產品間的直接通信看作引用,就可以将把程序内的單體應用看作特别的“分布式”應用。

事實上,大多數設計良好的單體應用都具備清晰的業務子產品邊界,而如何讓這些子產品以更加靈活的方式協作完成業務邏輯是設計中需要仔細考量的一個點,通信中間件就是一種不錯的解決方案。

現在大家應該對 ServiceAnt 是什麼有點認識了吧。

注:上面關于子產品如何劃分,我們團隊是采用的DDD,有興趣的同學可以移步我的另一篇博文,裡面分享了我們實踐DDD的一些經驗與基礎架構。傳送門點我

ServiceAnt 的現狀

 如上所述,目前 ServiceAnt 已經投入到我們團隊所負責的兩個線下産品線中使用,發現的坑也都填完了。

為了響應c#的開源氛圍,我重構了一下原有的代碼,并且補充了多版本的支援,然後上傳到了Gihub上。

目前版本号:1.0

支援 .net 版本:.net 4.5、net standard2.0

Github位址:點我進入

Github上有非常詳細的文檔,這裡我隻簡單介紹,ServiceAnt 支援的兩種工作方式:

  1.Pub/Sub(釋出/訂閱)模式,使用這種模式你可以把它視作事件總線。

使用 ServiceAnt 更好地解耦你的程式

  2.Req/Resp(請求/響應)模式,這種模式是我們工作中使用最頻繁的模式了吧,他跟普通的Http請求類似,發起一個請求然後可以由一個或多個處理函數(這些函數可能位于同一個子產品也可能位于多個子產品)來處理這個請求并傳回結果。

使用 ServiceAnt 更好地解耦你的程式

ServiceAnt正在完善例子和英文文檔,現在是起步階段,而且線下應用的需求也較為簡單,是以有很多功能都沒有實作(比如重試機制,流量監控以及可視化儀表等等)。

如果你有這樣的需求,歡迎在Issue上提出,我會在工作之餘第一時間回複你。

為什麼會有ServiceAnt

起因是這樣的,我們團隊在開發一個企業應用時采用了DDD,然後将我們的業務邏輯拆分為了複數個限界上下文,每個上下文低耦合高内聚的.

但無論再怎麼低耦合,總會有一些高層次的互動,這些被稱為“邊界點”,通常在分布式部署中,我們會選擇Webapi 或者 WebServie 等遠端通信手段來進行互動

遺憾的是,我們的應用是線下的,并發量也并不需要到叢集這樣重量級的解決方案,是以我們使用Abp的插件加載機制為基礎設施, 将每個上下文都實作成了一個個獨立的項目子產品.

項目初期我們使用 Abp 提供的事件總線作為子產品之間互動的方式, 但它有一個很不好的地方是, 它的事件引用必須是顯式的原對象引用。

這也就意味着,你為了在A子產品中使用B子產品釋出的事件,你必須讓兩個上下文都引用這個事件對象,這顯然加深了子產品間的耦合。

在參考了Abp, Medirator, NServerBus以及微軟的示例項目 EShopContainer 後,我決定自己實作一個服務總線, 它要具有以下特點:

  • 支援以委托的方式注冊處理函數
  • 支援 Req/Resp 模式
  • 事件的接收與釋出對象是非引用的(指你可以在不同子產品間建立各自的事件類,隻需要保證它們名稱與結構相同即可)

 是以ServiceAnt出現了, ServiceAnt 的初期目标是一個程序内的消息中介者, 後期有時間會逐漸完善它。

 Req/Resp 模式在上面已經介紹過了,可能很多同學比較有疑問的地方是:以委托的方式注冊處理函數這一點,請看下以下的代碼。

static void Main(string[] args)
        {
            var serviceBus = InProcessServiceBus.Default;
            serviceBus.AddRequestHandler<TestRequest>((requestParam, handlerContext) =>
            {
                Console.WriteLine($"Request Handler get value: {requestParam.RequestParameter}");
                handlerContext.Response = "First handler has handled. \r\n";
                return Task.FromResult(0);
            });

            // it used when you do not want to create trigger class, you can handle it with a dynamic parameter
            serviceBus.AddDynamicRequestHandler("TestRequest", (eventParam, handlerContext) =>
            {
                Console.WriteLine($"DynamicRequest Handler get value: {eventParam.RequestParameter}");
                handlerContext.Response += "Second handler has handled. \r\n";

                // set IsEnd flag to true then directly return response and ignore the rest handlers
                handlerContext.IsEnd = true;
                return Task.FromResult(0);
            });

            // this handler will not be excuted
            serviceBus.AddRequestHandler<TestRequest>((requestParam, handlerContext) =>
            {
                Console.WriteLine($"Third Request Handler get value: {requestParam.RequestParameter}");
                handlerContext.Response += "Third handler has handled. \r\n";
                return Task.FromResult(0);
            });

            var publishEvent = new TestRequest() { RequestParameter = "HelloWorld" };
            Console.WriteLine($"Send request parameter value: { publishEvent.RequestParameter }");
            var response = serviceBus.Send<string>(publishEvent);
            Console.WriteLine("The response is : \r\n" + response);

            Console.ReadLine();
        }

        class TestRequest : IRequestTrigger
        {
            public string RequestParameter { get; set; }
        }      

這段代碼是從Github上的示例代碼上複制過來的,可以看到它的所有處理函數都是以匿名委托的方式注冊的,并且示範了Req/Resp的管道工作方式。

Github上的介紹中也簡單寫了一些與其他類似元件的不同之處,有興趣的同學可以自行檢視。

寫在最後的話

ServiceAnt 離最初所定位的ESB還有很長的一段路要走,但因為目前公司的主産品是線下的自助系統及其支撐系統,是以一直沒有場景需求去開發支援分布式甚至是支援異構系統。

如果有哪些同學項目正好有這樣的場景又想使用 ServiceAnt,我很樂意與你一起分析需求然後完善 ServiceAnt 的功能,當然你也可以直接開發完之後發起PR給我。

目前網際網路的天下都被 Java, NodeJs, PHP等占了大半江山,導緻新出的 .Net Core 生存空間和生态都發展遲緩,雖然我不介意使用其他語言,但我更看好 .net core 和 c# 這個組合一些天生優勢(當然也有一點自己使用c#較多的情懷在裡面,呵呵),特别是它的設計和性能表現都可以稱得上後起之秀了,特别是2.0之後。

關于 .net core 我這裡就不多言了,已經偏題了,随手轉發一下最近在部落格看到的關于.net core 特性的文章吧。

英文版原版點我

熱心園友翻譯版點我

隻希望通過為c#的開源生态多貢獻一些東西,盡自己綿薄之力去改善它的生态。

這樣做不僅是為了大家其實也是為了自己,現在平均待遇偏低不說,更可氣的是整個大環境都讓人有些難受。

比如現在一個完全沒幹過程式設計,畢業五年的銷售,經過某些教育訓練機構教育訓練Java半年,履歷包裝一下,背背面試題,混進一個網際網路公司,他的待遇就要比很多.net的同等經驗工作者高。

為什麼?就是因為業界很多人都覺得 .net 還是那個無法做網際網路,封閉的老式技術,是以大環境下一說起線上應用就是 SprintBoot, SSM, SSH,導緻目前來看待遇更好,挑戰更多的網際網路公司都下意識選擇了Java。

我曾與公司的Java組同僚做過一些內建應用,自己也私下鼓搗過 SprintBoot,也了解過Java多數主流架構。

同時自己現在是web組的牽頭人,更多的時候是在做前端的技術工作,對比使用過的這些技術,我覺得現在的 c# 線上上應用方向的能力被很多人都看低了。

c#語言的優勢,我隻說一點,ES2015添加的箭頭函數早在c#3.0就已經有了,它就是lambda表達式,而java是在 java8之後才有的,c#語言由于誕生較晚是以吸取很多前車之鑒,加上設計者也很厲害,是以c#相較其他語言會更加優雅。Nuget也一點不比Maven,Npm差。IDE我就不多說了,用過Eclipse和Vs都懂,最新的Idea沒用過,但這裡不多做評論,隻是想說其他語言有的,c# 都不會差。

加之現在微軟大力推動開源與跨平台,我們有理由相信c#是可以線上上應用争得一席之地的。

是以如果你想作為一個c#的開發者能擁有更好的待遇與更多的挑戰,除了提升自己能力之外,多多貢獻自己力量去推廣它, 完善它的生态,讓整個業界重新認識它,也不失為良策,對吧。

歡迎轉載,注明出處即可。

如果你覺得這篇博文幫助到你了,請點下右下角的推薦讓更多人看到它。

繼續閱讀