關(guān)于組件聚合張力圖的討論
周三的午休時(shí)間,我在ThoughtWorks北京辦公室分享了一場《架構(gòu)整潔之道導(dǎo)讀》。當(dāng)談到分享組件聚合原則的時(shí)候,很多同事表示難以理解。究其緣由,是我們無法將組件違反原則的后果對應(yīng)到真實(shí)項(xiàng)目的問題上,這就導(dǎo)致原則和實(shí)踐之間的不一致。討論的過程異常激烈,但是很遺憾地最終并沒有得到一個(gè)服眾的結(jié)論。所以為了進(jìn)一步澄清這些爭議點(diǎn),我決定專門組織一場針對組件聚合原則張力圖的討論會。在吳大師的鼓動下,時(shí)間定在下周四晚上的8點(diǎn)半,與會人員大多是咨詢團(tuán)隊(duì)的技術(shù)教練,也有我們項(xiàng)目上的客戶。
在這場長達(dá)兩個(gè)半小時(shí)的討論會上,沒想到首先出現(xiàn)爭議的點(diǎn)居然是組件的定義。
組件是軟件部署的最小單元,是整個(gè)軟件系統(tǒng)在部署過程中可以獨(dú)立完成部署的最小實(shí)體。
對于這樣的定義,大魔頭提出了質(zhì)疑:library(庫)并不能獨(dú)立部署。但凡出現(xiàn)明顯的邏輯漏洞的時(shí)候,我們最好的方式是拋開譯文回去看原文。
Components are the units of deployment. They are the smallest entities that can be deployed as part of a system.
閱讀原文之后,我們發(fā)現(xiàn)“組件是軟件部署的最小單元?!边@句話翻譯得并沒有太大問題,但是第二句就有損原意了,原意是說可以作為系統(tǒng)的一部分被部署的最小實(shí)體,而沒有強(qiáng)調(diào)部署過程這種動態(tài)的概念,否則就和前一句是同義反復(fù)。所以這個(gè)定義里面并沒有說組件可以獨(dú)立部署。后面提到組件可以被鏈接到一個(gè)獨(dú)立可執(zhí)行文件或者歸檔文件,又或者,可以被打包成.jar、.dll或者.exe文件,并以動態(tài)加載的插件形式實(shí)現(xiàn)獨(dú)立部署。
解讀組件的定義
來自原文:
Components can be linked together into a single executable. Or they can be aggregated together into a single archive, such as a .war file. Or they can be independently deployed as separate dynamically loaded plugins, such as.jar or .dll or .exe files.
來自討論:
20:56:56 From tianjie : These dynamically linked files, which can be plugged together at runtime, are the software components of our architectures.
聯(lián)系上下文理解之后,我們知道:組件可以被設(shè)計(jì)成獨(dú)立部署的,但是并不是所有的組件都是可以獨(dú)立部署的。這是要澄清的,不然討論聚合原則的時(shí)候容易出現(xiàn)偏差。
吳大師接著解釋說,組件應(yīng)該是個(gè)邏輯單元,而不是物理單元。強(qiáng)制某個(gè)代碼模塊就是一個(gè)物理的部署單元是不合適的。另外,鮑勃大叔在介紹架構(gòu)邊界時(shí),也表明了一樣的觀點(diǎn):架構(gòu)的邊界并不是服務(wù)的邊界。
解讀REP原則
我按照自己的思路解釋過REP、CCP和CRP原則[1]之后,討論的焦點(diǎn)很快聚集到REP原則的解讀和實(shí)踐意義上。
吳大師認(rèn)為REP原則如果簡單解讀成沒有發(fā)布過程就不能復(fù)用,它就和CCP、CRP原則的排斥力量不均衡,無法形成穩(wěn)定的三角關(guān)系,那么這個(gè)張力圖就顯得有點(diǎn)雞肋。
尚奇受到CAP(分布式系統(tǒng)基本原理,一致性,可用性和分區(qū)容錯(cuò)性)原則的啟發(fā)提出了另一個(gè)解讀方向。他說,CAP原則在分布式系統(tǒng)的實(shí)踐里,都會先站住P原則,然后在C和A中權(quán)衡。那么在REP、CCP和CRP三角關(guān)系里,REP原則就相當(dāng)于這里的P原則,必須先滿足然后再去取舍CCP和CRP。
大魔頭理解REP的意思是可復(fù)用性就是組件是獨(dú)立可復(fù)用的。假如回到?jīng)]有Maven這些工具,沒有依賴管理的年代,如果我們所依賴的包還依賴其它第三方包,那么這個(gè)包就不能叫做獨(dú)立可復(fù)用。
21:13:04 From YangYun : 我倒是理解REP的意思是你發(fā)布出來的一個(gè)可重用的包就是獨(dú)立可重用的,你不能讓我必須帶著別的jar包才能用它。
21:14:04 From YangYun : The granule of reuse is the granule of release
他接著說,假如有兩個(gè)提供同樣功能的包,其中一個(gè)沒有第三方的依賴,而另一個(gè)有,那我當(dāng)然選擇前者。
技術(shù)教練Sara舉出了一個(gè)相對復(fù)雜但是很有啟發(fā)性的例子。
21:46:35 From Qian Ping : 假設(shè)項(xiàng)目包含sub module ABC
- 如果ABC單純sub module沒有打成jar,又互相直接復(fù)用了,就是違反了REP
- 如果每個(gè)sub module,打成jar,互相復(fù)用的時(shí)候是通過對方特定版本的jar(如snapshot版本),就是符合REP
- 如果符合REP了,而所有sub module是跟隨整個(gè)項(xiàng)目一起升級版本,就是符合CCP因?yàn)樗麄兪且惑w一起發(fā)布的
- 這時(shí)假如A依賴B和C,我這次單純想改C,他們一起升版本了。但其實(shí)B的Jar完全沒有變化,這個(gè)對B來說就是一個(gè)不必要的發(fā)布,B又貌似應(yīng)該分離出去,但如果它分離出去了,就又離REP和CCP遠(yuǎn)了
對于最后一句的表述,她澄清道:
之前有遇到一個(gè)情況,比如組件A,然后它里面需要用到一個(gè)common library, lib里面其實(shí)包含了比如3個(gè)sub module(1/2/3),全部都是A需要復(fù)用的, 這時(shí)候如果要改1/2/3里面任意的東西,都會一起升級lib,然后在A里面對應(yīng)升級版本。
后來,有一些新組件B,它只需要用到common lib里面的3,不需要1/2,于是3一直被改和打包版本。 此時(shí)1/2會跟著升版本號,但其實(shí)1/2內(nèi)容本身是完全沒有變化的,只是版本號升了。
這個(gè)場景中引入了兩個(gè)組件A和B分別依賴common library的某些模塊。在我們討論一個(gè)組件依賴時(shí),面臨的約束要簡單很多,但是復(fù)用的初衷就是給多個(gè)組件去依賴,所以這個(gè)假設(shè)是很有價(jià)值。
Sara分析的思路如下:
如果分離出去,等于我有兩個(gè)common lib(1/2 和 3), 對于B來說,B只需要3這么一個(gè)lib是比較完美的,反正改了3再改B就好了。
但對于A來說,它就需要同時(shí)升級1/2的lib和3的lib,等于要3個(gè)發(fā)布,而它原來只需要2個(gè)發(fā)布(1/2/3 + A),所以離CRP遠(yuǎn)了,同時(shí)它也要分別維護(hù)兩個(gè)lib分別的版本升級,所以CCP也比原來差了。
在她的分析下,我們發(fā)現(xiàn)CRP和CCP不單是互相排斥的,還有可能兩者都無法滿足。造成這種結(jié)果的原因在于1/2/3模塊形成的這個(gè)common library對于A組件而言都符合CCP和CRP原則,但是對于B組件而言,是不滿足REP和CRP原則的,因?yàn)槊看蜗胍蕾?模塊,就得全部依賴1/2/3整個(gè)common library(復(fù)用困難)。反之,如果我們將3從1/2/3中拆出來成為獨(dú)立的組件,那就幾乎宣告對于A組件而言勢必違反CCP和CRP原則,但是B組件卻獲得了符合REP和CRP原則的好處。
她接著補(bǔ)充道:
其實(shí)后來說起對應(yīng)微服務(wù)的時(shí)候有另外一個(gè)想法,就是比如說我系統(tǒng)里面多個(gè)組件需要用計(jì)提(Mark to market[2])這么一個(gè)功能,說白了就是一條公式,那通??梢杂袔讉€(gè)做法
- 直接把這個(gè)公式復(fù)制到要用的組件,code level的復(fù)用,沒有版本 -> REP bad, CCP bad, but CRP not bad (因?yàn)橐臅r(shí)候發(fā)布次數(shù)還是一樣的)
- 把公式寫到一個(gè)common lib里面再進(jìn)行復(fù)用 -> REP good, CCP good, CRP bad(多發(fā)布一次)
- 把公式放在一個(gè)獨(dú)立service -> REP good, CCP bad(因?yàn)橐S護(hù)多一個(gè)服務(wù)), CRP good
這個(gè)觀點(diǎn)就上升到不同層次的復(fù)用性上,可以算是對組件聚合原則的普適性的探索。
當(dāng)話題再次被聚焦到復(fù)用性時(shí),技術(shù)教練MoMo提出一個(gè)觀點(diǎn):我們現(xiàn)在討論就是可復(fù)用組件應(yīng)該遵循的原則,而REP是對復(fù)用粒度的定義。至于那些那些常年采用SNAPSHOT(Java項(xiàng)目里Maven常用的開發(fā)版本號),沒有發(fā)布概念的組件,就不該納入復(fù)用的考慮范圍內(nèi),那些也就不是REP的反模式。
與此同時(shí),閻王指出了一個(gè)翻譯上的失誤。組件粘合張力圖中REP原則的簡短描述是“為復(fù)用性而組合”,而原文其實(shí)是"Group for reusers",翻譯過來應(yīng)該是為了復(fù)用者而組合,復(fù)用性的英文是 Reusability。所以為了復(fù)用者發(fā)布,考慮的就是對外部的承諾。

外部資料
大魔頭在加班寫方案和討論的間隙,快速查閱了一些資料,比如wiki上對于REP原則的定義:
21:45:05 From YangYun : Reuse-release Equivalence Principle (REP)
REP essentially means that the package must be created with reusable classes – “Either all of the classes inside the package are reusable, or none of them are”. The classes must also be of the same family. Classes that are unrelated to the purpose of the package should not be included. A package constructed as a family of reusable classes tends to be most useful and reusable. - wiki百科里
在wiki的定義里,可以看到REP原則包含CRP和CCP原則的成分,如此看來,這三大原則并不符合MCME分類原則,就連鮑勃大叔在書中也是模棱兩可的態(tài)度——REP維護(hù)共同的大主題,組件中的類和模塊也必須緊密相關(guān),這基本是CCP和CRP的簡版描述。
然后大魔頭查找到“粒度”這個(gè)詞在軟件設(shè)計(jì)中詳細(xì)定義,這是對REP原則定義(軟件復(fù)用的最小粒度等同于其發(fā)布的最小粒度)的分解和再認(rèn)知。
21:57:03 From YangYun : http://condor.depaul.edu/dmumaugh/OOT/Design-Principles/granularity.pdf

21:58:29 From YangYun : https://fi.ort.edu.uy/innovaportal/file/2032/1/design_principles.pdf

這些觀點(diǎn)和學(xué)術(shù)建議很有代表性,值得大家反復(fù)揣摩和思考。
反模式
軟件工程師一般有個(gè)“正難則反”的習(xí)慣。原則較抽象,但是模式很具體,反模式更能指導(dǎo)實(shí)踐。接下來,大家開始討論哪些是違反了REP原則的反模式。
首當(dāng)其沖的就是git submodule,在某些項(xiàng)目中,這種通過源代碼劃分模塊并共享的方式還是挺常見的。因?yàn)楣蚕淼氖谴a,所以每次共享代碼更新,勢必要讓依賴方重新編譯,發(fā)布和部署。這種做法對于復(fù)用是痛苦的。
其次是常年使用SNAPSHOT版本的某些項(xiàng)目。這些項(xiàng)目的特點(diǎn)一般都是某個(gè)產(chǎn)品團(tuán)隊(duì)底下,內(nèi)部團(tuán)隊(duì)之間有復(fù)用的要求。缺點(diǎn)其實(shí)也很明顯,常年SNAPSHOT等于沒有版本和發(fā)布的流程。使用者并不知道SNAPSHOT中哪些是穩(wěn)定的,哪些是修改的,拿到的版本到底是最新的還是遺留的,我需要的功能在這個(gè)功能有包含,還是你包含了太多我不需要的升級。這種也是復(fù)用痛苦的。
REP原則小結(jié)
綜合以上兩個(gè)例子以及其它討論,我們得出了一個(gè)好玩的結(jié)論:軟件工程發(fā)展到現(xiàn)在,REP原則已經(jīng)是基本的要求,它的存在有可能是鮑勃大叔年代感老了的體現(xiàn)。
[1] 架構(gòu)整潔之道導(dǎo)讀(一)編程范式
[2] 架構(gòu)整潔之道導(dǎo)讀(三)組件耦合
于 2018-11-12