1. 程式人生 > 其它 >訊息驅動

訊息驅動

一、訊息處理的command模式

首先設定一個訊息包結構中必包含key欄位,表明該訊息需要由什麼處理器來處理。

在aspnetcore-webapi中,通過解析http訊息包中的請求url獲得path,最終路由確定處理函式在controller-action中。

在有些框架中,喜歡使用command模式來確定處理函式,而處理函式被封裝在獨立的command類中由處理函式轉化為了處理類。

我曾經一度非常迷戀這種command模式。因為對產品邏輯的輕視,我認為業務程式碼是非常容易變動的,在進入了command之後不需要在使用任何設計模式了。甚至應該在之後使用動態語言做指令碼化來實現command的邏輯,以使線上服務能夠即時更新或修復邏輯問題,後來這個問題從另一層面也就是從微服務滾動更新的方法也算是得到了一定程度的解決。

二、command模式的實現

最初的時候command模式是整合在網路框架中的,從tcp讀取出資料後拆包、反序列化、處理一氣呵成。後來提煉成三個模組,於是訊息處理這塊就得以跟網路解耦應用到其他地方成為訊息驅動的命令模式。

1)實現的方式簡單來說就是定義一個抽象類CommandBase,定義抽象處理函式。因為是訊息驅動,所以訊息必須是強型別,key->訊息型別->處理類。所以CommandBase是泛型類,泛型引數為要處理的訊息型別。

2)然後通過反射獲取指定程式集Assembly中的所有CommandBase<TRequest>的實現類,建立例項,存入字典。

3)寫一箇中介者型別,實現一個派發函式,引數為訊息例項,從字典中撈出例項型別對應的Command,呼叫處理函式。

三、幾個問題點和暫行方案

整個的實現思路非常簡單。現在分析下其中的幾個問題。

1)首先是第一步中的CommandBase<TRequest>是個泛型抽象類。這限定了所有的Command都必須是其子類,這通常是沒什麼的問題。只是這個抽象類除此之外給不了什麼其他的資產可以繼承了,實屬貧乏,更好的做法是定義為泛型介面ICommandBase<TRequest>。

2)接著是第二步中的字典,即使定義為泛型介面,那也是泛型呀,因為是泛型輸入引數所以又不能協變為ICommandBase<object>,所以只能做為object存入字典嗎?

第一種解決方案是還是定義一個非泛型的基礎抽象類CommandBase,用object作為處理訊息型別,由其子類CommandBase<TRequest>重寫,轉化object為泛型引數TRequest後呼叫泛型處理函式。只是這樣又回到了問題1中。

第二種方案是不增加基類,而是使用裝飾器,也就是另一個類比如CommandWrapper和它的泛型型別CommandWrapper<T>,讓ICommandBase<T>作為其一個屬性,將wrapper存入字典中。Wrapper實現一個訊息處理函式呼叫其屬性command的執行函式。這樣基類的問題就轉移給了這個包裝類,而包裝類是模組內部封裝的,對外不可見。

3)生命週期的問題。Command應該作為單例嗎?在之前的敘述裡,字典中存放的已經是command例項了,所以一直是作為單例存在的。可是如果是單例的話,命令類是單例而處理函式是瞬時的,會造成一點誤解,以及在實現某些需求,在注入的依賴型別的使用上會遇到一些生命週期衝突的問題。

我最終的辦法是把生命週期和例項化的問題都丟給DI容器也就是IServiceCollection,也就是由使用者自己決定是通過哪種方式注入中介者型別,而Command例項也伴隨著中介者型別的生命週期。在我跟我的網路處理框架整合的時候,我習慣將它的scope設定為跟網路連線的虛擬會話生命週期保持一致,這樣可以在command中順利獲取到當前關聯的會話。