在團隊中推廣面向介面開發兩年左右,成果總體來說我還是挺滿意的,使用面向介面開發的模塊使用Unity容器配置的功能非常穩定,便於共用遷移(另一個項目使用只需要複製配置和調用介面即可)也很好擴展(操作的資料庫、表、資源等都可以配置)。 但是由於當時開發的匆忙(邊開發邊應用),留下一些比較致命的問題: 1 ...
在團隊中推廣面向介面開發兩年左右,成果總體來說我還是挺滿意的,使用面向介面開發的模塊使用Unity容器配置的功能非常穩定,便於共用遷移(另一個項目使用只需要複製配置和調用介面即可)也很好擴展(操作的資料庫、表、資源等都可以配置)。
但是由於當時開發的匆忙(邊開發邊應用),留下一些比較致命的問題:
1、很多介面定義的不合理,通用性和擴展性不好
2、固定死了使用Unity容器,如果更大面積推廣有問題,有些人已經很熟悉其他容器了,再來重新學Unity沒有必要
3、配置比較麻煩,需要簡化
所以我覺得有必要重新開發一個框架,對原框架取其精華去其糟粕,再吸收開源項目(含微軟開放源代碼的部分),爭取做出一個像樣一點的東西並開源出去...
這裡插一句,前面有寫一個Asp.net Mvc分區擴展框架系列沒有寫完的原因,其一是趕時間做這個框架,其二是重新做新框架,所有剩下的文章按新的框架來寫會更好
這裡要講的是本框架中一個小的組件,應用程式上下文組件(AppContext),本組件有參考博客園大神的文章,這裡先不註明出處(如果文章的作者介意,我再修改博文標註)
下麵開始介紹本組件的主要使用場景
1、不同對象和方法間傳遞數據的新方式,看例子
看運行結果:
有人會說,你就作吧,修改一下A方法返回那個隨機數,修改B方法增加一個參數接受這個隨機數,So Easy。
但是在現有完成架構上增加一個附屬功能,就把簽名改了是很恐怖的事情。實際項目中產生對象方法和其他需要調用對象的方法可能沒有調用鏈,或者調用鏈非常複雜,你要把涉及的所有方法都改一邊,那系統估計差不多要被你改"廢"了
在現實項目中並不推薦大規模的這樣使用,只有在擴展附屬功能,Aop或者在現項目擴展需要,慎重之下一個解決方案而已,性能肯定是比直接傳要差不少,權當是應用程式上線文緩存來使用。
以上代碼按照預期的效果運行了,看似簡單功能,後面原理還是挺複雜的
2、線程安全調用
以上用兩個線程對上下文中讀取和寫入數據,看數據是否會串(線程是否安全),看執行結果:
稍微解讀一下:
A:線程6先運行,從上下文中讀取鍵為test類型string的值為空,阻塞1000毫秒,再寫入鍵為test,類型為string的值1000,再阻塞1000毫秒
B:線程11從上下文中讀取鍵為test類型string的值為空,阻塞14毫秒,再寫入鍵為test,類型為string的值14,後阻塞14毫秒
C:線程11從上下文中讀取鍵為test類型string的值為14
D:線程6從上下文中讀取鍵為test的string的值為1000
E:兩個線程交替運行,各自讀取和寫入的值互不影響
所以建議定義全局靜態變數的就省省吧,那樣線程不安全,危險很大
3、有人會提到HttpContext
確實HttpContext.Current.Items可以實現這樣的效果,但是HttpContext.Current並不是無處不在,非Web應用程式為null
就算是Web項目也不能濫用HttpContext.Current,在非同步線程裡面該屬性也是null,不信的可以試試
但是HttpContext.Current還是很有用的,我這裡實現的AppContext就有調用HttpContext.Current,如果為null,再調用另一個更神秘的東西CallContext
大家看一下實現代碼:
在web程式中有非同步操作會有線程切換,需要線程間複製上線文對象,使用HttpModule(以下代碼來自一個博客園大神,我幾乎沒改)
4、服務(配置)作用域
4.1 先看建模代碼
稍微解讀一下:
A:Greet是用來打招呼,name對who發一個問候語
B:Person明顯就是代表一個人,其中有一個Chindren屬性,代表從屬子對象(為了簡化使用相同類型)
C:Person有一個Sayer屬性,用來對其他"人"打招呼的
D:從方法Greet(Greet sayer, Person person)可以看出,如果取不到sayer對象會打招呼失敗(提示sayer is miss)
E:對另一個對象Great的時候會遍歷每個子對象分別對那個對象"問候"一下(這家的小孩還真有禮貌,大人問候誰,每個小孩都問候他)
4.2 再看測試代碼
也解讀一下:
A:定義了a,a1,b及a的另一個子對象
B: 其中只有a對象設置了Sayer屬性
C:a對象Great了b(其中隱含a的所有子對象都對b進行Greate),之後a1對象單獨Greate了一下b
4.3 看運行結果
哈哈,看到結果是不是有點驚訝啊
A:a對b的Hello是成功的很正常,但是a的兩個子對象也對b的Hello成功了
B:更神奇的是,a1單獨對b的Hello缺是失敗的(不是繼承了父對象的屬性)
C:這裡對上線文又進了一步,是作用域,只有a對b使用自己的Sayer屬性時,其他對象(不一定是自己的子對象)也可以截獲Sayer的值,但是等a對象使用完畢後就回收了,其他人就用不了了
4.4 原理解析
這裡有一個using的作用域,AppContext.CreateResource在上線文中註冊或者查找類型為Greate名字為A的,候選值為Sayer的對象,保存在resource.Entity中
4.5 來看一下實現的源代碼
哈哈,是不是有點複雜啊
A:實現IDisposable以便使用using來控製作用域
B:Dispose也不是簡單的就把作用域的對象刪除,如果_needDispose為false不刪除對象(不是自己創建的對象無權刪除)
C:除了刪除有的時候還要Restored(_needRestored為true),自己的作用域結束,把備份的_entity0還回去
D:Init方法嘗試從作用域獲取對象並備份到_entity0,如果本次候選對象無效直接使用作用域已有對象,如果有效就覆蓋作用域(有_entity0備份能還原回來)
E:是不是非常有意思,就像定義變數一樣,小作用域可以覆蓋上級作用的變數,離開小作用域,外面作用域的變數又可以使用了
5、作用域的作用
5.1 我特意開發這個功能是用來簡化系統配置的
5.2 比如日誌(含異常調試日誌)幾乎是每個對象都需要的功能
如果每個對象都配置這些通用屬性,配置的工作量太大了,配置文件太亂了
如果硬編碼寫死,那豈不就倒退到八百年前了
並不是所有都不配,只把特殊需要的配置,如果需要日誌在最外面配置(或者只初始化一個作用域),裡面調用的子對象也都可以共用外層的配置(共用日誌服務)
有人可能說衝突了怎麼辦,可以用name隔離
6、應用程式上下文緩存
這個緩存的作用域只能是當前會話(線程或者Request),所以天生的線程安全(所以那個字典我都沒用ConcurrentDictionary)
有人可能要笑話我,緩存不都是分散式嗎?你那個緩存利用率太低了吧?
是的,你要說緩存利用率,確實不能和分散式比。但是要想到這個比session還session(不用清除下次請求重新初始化,也不用擔心被人杜撰(修改sessionId))的高速緩存(直接字典)
自然也和session一樣不用擔心用戶數據會串、存放用戶登錄狀態、許可權等都不是問題
用起來簡單的不得了,直接New就可以使用,哪怕定義一個全局靜態變數都是“線程(會話)安全”的,也可以配置在容器裡面放心使用
該組件暫時就先寫這麼多吧。寫這篇文章的目的除了給大家分享自己的東西外,最主要的還是想得到博客園大神的指導,因為框架一旦開始使用再做調整就很要命,其一希望框架能做的更通用更好用,大家一起使用。其二把可能的問題儘早的發現,在正式使用之前更加完善。有些硬傷,比如介面、方法簽名定義等如果不合理推廣後就不好修改了,可能只能作為框架的缺陷存在了。介面一改,依賴這個介面的所有實現類都要改,很恐怖的。也是因為原來開發匆忙的框架“缺陷”太多,我不得不下定決心開發新框架。
這麼多天每天長時間寫代碼,每天加班加點都寫得全身冒汗,也寫出了不少代碼,卻是今天下班後第一次調試,並且調試通過了一個我非常喜歡的上線文作用域組件(當然還有優化的地方)
下麵簡單介紹一下這個框架:
以下是項目解決方案截圖:
A:第一張圖是解決方案,部分擴展功能項目,這些需要很多人參與進來完善
B:後面的四張圖是框架主項目的截圖,大部分都是已經開發完成但還未測試的功能,其後是大量測試改bug和完善功能
主框架及相關項目我會在這裡系列裡面逐個測試優化後在發佈出來,希望大家一起幫忙看一下,提供意見和建議
C:主框架本身並不力求解決所有方法,而是定義一些通用功能、邏輯的介面,對部分介面提供預設實現及部分“設計模式”功能,其他功能在其他項目中再擴展實現
D:主框架不含對具體哪種資料庫或者哪種ORM工具的調用和不含具體開源外部日誌組件和容器等調用(這些應該在外部擴展項目中對接)
E:該框架並不是要推翻系統的東西,而是整合,增加擴展性,給程式員帶來更多便利和選擇
F:總之主框架主要是抽象主要介面、實現部分非常通用的功能,儘量少引用外部
G:其他功能儘量繼承按主框架的介面來開發,以便方便集成到主框架,或者其他模塊間相互調用卻不相互引用