在我的眼中,過(guò)去數年間的多股潮流,都指向同一個(gè)方向,現在該到將它們總結歸納的時(shí)候了。這篇文章沒(méi)有什么新奇觀(guān)點(diǎn),只是將我的思考與觀(guān)察做一個(gè)概括。我不自禁要將心中的圖景描繪出來(lái)(即便這幅畫(huà)的背景全由我的痛苦教訓涂抹而成)。這就開(kāi)始吧。
即使過(guò)了16年,我依然清楚記得[Booch OOAD]書(shū)中討論如何使用分層與分塊(有的讀者可能更偏好"模塊"這個(gè)同義詞)。那時(shí)候我開(kāi)班講授一門(mén)面向對象分析與設計的課程,這本書(shū)是教材。我感覺(jué)討論分層很容易,因為當時(shí)接觸甚多(后來(lái)仍然深受影響,下一節會(huì )談及),但要討論分塊就有點(diǎn)難。
在我參與的實(shí)際項目中,我們確實(shí)一直采取分塊的做法,但講授的時(shí)候很難用短小的例子示范分塊。而且就我記憶所及,我們采取分塊只是出于技術(shù)上的限制,并不像分層那樣是自然而然地發(fā)生的。.
分塊沒(méi)能“物盡其用”的感覺(jué)一直伴隨著(zhù)我,我也就聽(tīng)之任之。
2001年寫(xiě)第一本書(shū)[Nilsson NED]的時(shí)候,我對分層的盲目溺愛(ài)到達頂峰。圖1的分層示意圖已經(jīng)是簡(jiǎn)化過(guò)的版本。

圖1. 過(guò)去的典型分層(簡(jiǎn)化版本)
圖1可見(jiàn),中間層包括了Fa?ade層、業(yè)務(wù)邏輯層和數據訪(fǎng)問(wèn)層。在數據層還有公共存儲過(guò)程(sproc)層、私有存儲過(guò)程層,有時(shí)候數據表之上還有視圖層……(UI部分也是分層的,可想而知……)。
我把這種分層方案稱(chēng)為我的“默認架構”,顧名思義,我每次著(zhù)手新項目都以此作為出發(fā)點(diǎn)。當然具體的架構形態(tài)隨著(zhù)時(shí)間會(huì )有所變化,但重點(diǎn)是我先入為主地認為項目都離不開(kāi)這種嚴格的分層方案。
我觀(guān)察到:
如今,我的典型初始分層方案與以往截然不同。一開(kāi)始只用一個(gè)領(lǐng)域模型層,先把握住我們真正關(guān)心的部分,也就是業(yè)務(wù)問(wèn)題的解決方案。
然后當有必要的時(shí)候才增加層次,比如需要用戶(hù)界面的時(shí)候才會(huì )加上UI層。只要合適,UI層甚至可以直接疊在領(lǐng)域模型之上。要是不合適,再考慮別的需要。
接下來(lái),如果我們希望把領(lǐng)域模型持久化到關(guān)系數據庫,才把這部分需要放入場(chǎng)景中加以考慮。通常用一個(gè)對象/關(guān)系映射就能解決得很好,大約能自動(dòng)解決80%的領(lǐng)域模型到數據庫的數據映射需求。請看圖2。

圖2. 時(shí)下典型分層一例
我還嘗試令數據庫Schema成為領(lǐng)域模型反映出來(lái)的“結果”,自動(dòng)生成數據庫Schema。這樣做意味著(zhù)避免了大量繁重的工作,讓我們有時(shí)間投入到真正關(guān)心的部分——領(lǐng)域模型本身。(同樣的做法還可以用到UI上。)
如此一來(lái),我不用再拘泥嚴格的分層方案,轉而把精力集中于尋找恰當的分塊。又由于整個(gè)解決方案被分為若干小塊,從而每一個(gè)分塊的分層形式可以更加靈活,因為規模更小了。
寫(xiě)上面一節的時(shí)候,我想起了2005年在挪威Lillehammer舉行的軟件架構研討會(huì )[Fowler LayeringPrinciples]。當時(shí)我們在會(huì )上討論如何把大的團隊拆分成若干小團隊。
無(wú)論那次會(huì )議之前或之后,我都有拆分團隊的經(jīng)驗,多次拆分中既有按技術(shù)拆分的,也有按功能拆分的。我尤其記得一個(gè)項目,對其中一名團隊成員來(lái)說(shuō),按功能拆分是失敗的,因為他沒(méi)有什么經(jīng)驗,所以幾乎每一個(gè)方面他都不得不苦苦掙扎。.
在另一個(gè)項目中,按技術(shù)拆分看起來(lái)非常成功,不過(guò)那個(gè)項目的成員都很有經(jīng)驗。即使在這個(gè)成功項目里,也有很多情況如果按功能拆分會(huì )取得更高的效率。比如跨單元的小改動(dòng),牽涉到的人可以更少。
總而言之,我偏向按功能拆分團隊。每個(gè)人肯定有自己特別擅長(cháng)的部分,但如果不用協(xié)調及等待其他人完成相應的工作,那么大部分工作都可以更快地完成。
前些年很流行建立整個(gè)企業(yè)“一統天下”的數據模型。背后的想法是如果一朝找到并描述出這樣的數據模型,就能從中創(chuàng )造出巨大的業(yè)務(wù)價(jià)值。于是傳達給業(yè)務(wù)人員的是這樣的信息:
“現在給我們兩年時(shí)間不受干擾,到時(shí)候我們會(huì )交給你一個(gè)定義好的模型,你想要的一切都能不費吹灰之力就創(chuàng )造出來(lái)?!?/p>
依我之見(jiàn),“企業(yè)數據模型”是大大失敗了。原因肯定不少,但下面這幾點(diǎn)可能在最重要之列:
我個(gè)人強烈相信大的任務(wù)要一口一口地啃,而上下文是王道。
A前面說(shuō)過(guò),我認為建立企業(yè)數據模型的嘗試常常以失敗告終?,F在我又發(fā)現一種有點(diǎn)諷刺的情形——還有項目幾乎在重復同樣的嘗試,只不過(guò)這一次換成了“企業(yè)領(lǐng)域模型”。論據沒(méi)變,我想結果和造成結果的原因也不會(huì )變……
公平來(lái)說(shuō),更常見(jiàn)的情況倒不是真的追逐“企業(yè)領(lǐng)域模型”,而是想建立單一的大型領(lǐng)域模型。此外,團隊不時(shí)發(fā)現有需要切分大的領(lǐng)域模型,但又發(fā)現不容易找到滿(mǎn)意的方案。
無(wú)論如何,我都強烈建議當領(lǐng)域模型出現增大跡象的時(shí)候,將它分塊。還有別忘了模型是有上下文的。
很有趣,我還聽(tīng)聞一些非常大的SOA項目采取的第一個(gè)步驟就是打算建立一套“企業(yè)文檔模型”。它會(huì )比企業(yè)數據/領(lǐng)域模型更成功嗎?要是打賭的話(huà),我寧愿下注在另一邊。
就算有意避免巨型的單一領(lǐng)域模型,把模型隔離成幾個(gè)部分,每一個(gè)部分還是有可能膨脹到相當規模。發(fā)生這種情況的時(shí)候,往往會(huì )發(fā)現數據庫并沒(méi)有分塊。各個(gè)領(lǐng)域模型置于同一個(gè)數據庫之上,也就是共用一個(gè)整合的數據庫,見(jiàn)圖3。

圖3. 分塊的領(lǐng)域模型,使用整合數據庫
把多個(gè)領(lǐng)域模型置于單一數據庫之上,只是整合數據庫的其中一種情形。實(shí)際上跟多個(gè)獨立應用共享同一個(gè)數據庫是一樣的。類(lèi)似情況可謂屢見(jiàn)不鮮。
很不幸這樣的安排給維護造成了極大的負擔。任何影響到數據庫Schema的改動(dòng)都必須同步修改相應Schema的所有消費者。消費者越多,情況越糟。往往造成改動(dòng)被限制到最低程度,很可能進(jìn)而導致從代碼中榨取的業(yè)務(wù)價(jià)值大大減少。
大膽地說(shuō)一句,如今在我眼中,整合數據庫是一種反模式。啊,總算說(shuō)出來(lái)了,感覺(jué)真好。
那么,有什么別的辦法呢?我認為領(lǐng)域模型的分塊應該一直延續到數據庫,讓數據庫也遵循同樣的分塊方案。這種與整合數據庫[Fowler IntegrationDatabase]相反的模式稱(chēng)為單應用數據庫[Fowler ApplicationDatabase],見(jiàn)圖4。

圖4. 分塊的領(lǐng)域模型,使用各自的單應用數據庫
分塊之間的通信是通過(guò)領(lǐng)域模型之上的服務(wù)完成的,不允許抄捷徑直接訪(fǎng)問(wèn)其他領(lǐng)域模型的應用數據庫。這意味著(zhù)可以(也通常會(huì ))有效地將不同的應用數據庫部署到不同的服務(wù)器上(或者部署到云中)。
一般認為報表是整合數據庫的優(yōu)勢所在。要是換一種方式去處理報表,可以把它看成一個(gè)獨立的應用,有著(zhù)自己的數據存儲。數據從其他分塊搜集而來(lái)。其中一種辦法是將每個(gè)分塊都看作一個(gè)提供事件源的應用[Fowler Events],讓那些事件源都吐出報表應用感興趣的事件就可以了。
順便一提,上面的討論令我記起曾經(jīng)為ADDDP[Nilsson ADDDP]寫(xiě)過(guò)一段文字[Nilsson Bricks](不過(guò)最終沒(méi)有放進(jìn)書(shū)里),介紹另一種應用數據庫的用法。不過(guò)當時(shí)寫(xiě)作的重點(diǎn)落在性能,而非可維護性。文中我介紹了如何根據系統不同部分的特點(diǎn),讓系統發(fā)揮最大的功效。
再順便一提,一定要讀一讀Greg Young寫(xiě)的DDDD系列文章[Greg Young],他的精彩作品也可以歸到這個(gè)主題之下。他建議嚴格切分讀和寫(xiě),籍此取得非常高的可伸縮性等等成效。
論點(diǎn)其實(shí)差不多,只不過(guò)用到UI上沒(méi)有整合數據庫vs.應用數據庫那么有力。與其用一個(gè)UI套上幾個(gè)服務(wù),何不讓每個(gè)服務(wù)都有自己的UI,然后將不同的UI用一個(gè)UI容器框架集成起來(lái)?見(jiàn)圖5。

圖5. UI也分塊
這樣一來(lái),某個(gè)分塊的開(kāi)發(fā)者可以全方位地處理問(wèn)題,從UI一直到存儲,不必與其它團隊同步。對實(shí)際生產(chǎn)率的潛在影響是巨大的。
經(jīng)常有人向我們求助,抱怨說(shuō)他們100人的大團隊沒(méi)辦法達到期望的生產(chǎn)效率。
每一次,我都得到同樣的結論。不要那樣做!100名開(kāi)發(fā)者在同一個(gè)團隊里,太可怕了,失敗風(fēng)險太大了。即使你再加20個(gè)人!
雖然已經(jīng)出版了30年,讀過(guò)《人月神話(huà)》[Brooks MMM]的人還是少得出奇。真糟糕。(書(shū)名“人月神話(huà)”的意思是,向一個(gè)進(jìn)度落后的項目增加更多開(kāi)發(fā)者,只會(huì )讓它更落后。書(shū)里還闡述了很多重要的思想。)
所以,就算退一步說(shuō)你真的有一個(gè)規模龐大的問(wèn)題要解決,你也真的需要100名開(kāi)發(fā)者。請一定要小心謹慎地將大團隊分成若干個(gè)小規模的、盡可能相互隔離的團隊,以便每個(gè)團隊能夠全速運行。
但是當你把大團隊分成比如說(shuō)100個(gè)新團隊的時(shí)候,顯然不會(huì )因此就消滅了復雜性。例子里面分成10-20個(gè)團隊比只有1個(gè)團隊好,并不表示分成100個(gè)會(huì )更好。請注意平衡。
說(shuō)到平衡,兩年前我曾經(jīng)很不理解SOA的一些說(shuō)法,還寫(xiě)了博客帖子[Nilsson SOA-Qs]請教答案。
我不理解的其中一點(diǎn)是為什么要極端的細粒度。為什么不用“定界上下文(Bounded Context)” [Evans DDD]的思維來(lái)看待服務(wù)呢,為什么需要粒度細到只有一行代碼的服務(wù)……
Jim Webber在一篇文章里[Webber Anemic Service Model]討論過(guò)這個(gè)問(wèn)題,我認為他說(shuō)得很有道理。對松散耦合的關(guān)注成了唯一的決定因素,以至于忘了搭配上它的好朋友——高內聚,才導致出現令我困惑不解的奇怪說(shuō)法。
所以,我把前面討論的分塊看作是“做對了的SOA”。
最初我把標題定為“強迫一種信仰”,但又顧慮到Google帶來(lái)的很多人會(huì )失望。我在這里談的風(fēng)格/信仰,是指我們當中有很多人喜歡思考和討論TDD、DDD、BDD、模式、重構、干凈的代碼等等。但世上還有更多開(kāi)發(fā)者并不同意我們所說(shuō)的是“唯一的道路”。
有時(shí)候會(huì )采取的解決辦法是完全不理會(huì )實(shí)際情況,強迫所有的開(kāi)發(fā)者都用同一種風(fēng)格。會(huì )發(fā)生什么事呢?我猜下面這幾種情況會(huì )很常見(jiàn):
能保持一致是好的,但不能不惜代價(jià)去保持一致。實(shí)際上我覺(jué)得很多時(shí)候讓不同的分塊用不同的風(fēng)格開(kāi)發(fā),反而有好處。不同開(kāi)發(fā)者之間的技能差異是其中一個(gè)原因。還有另一個(gè)經(jīng)常被忽視的原因,不同的分塊當然是有差異的,所以相應地采取不同的風(fēng)格反而有利。比如有的分塊從領(lǐng)域模型方式中肯定得不到什么好處,那么不采取領(lǐng)域模型的路線(xiàn)也沒(méi)關(guān)系,實(shí)際上也建議不要那么做!
即使放寬了對分塊內部的約束,你還是可以(也應該)對外部表現設立嚴格的要求,比如要保證自動(dòng)化測試成功。
我意識到以上做法違反了集體代碼所有制的原則,因為人們都圈在自己的分塊里。在分塊內部,(如果該分塊選擇了集體所有的風(fēng)格)肯定是代碼集體所有的。
對了,在單個(gè)分塊內,我認為應該強迫一種風(fēng)格。
這件事情你可能覺(jué)得顯而易見(jiàn),不過(guò)我還是要特別指出來(lái):不同分塊的變化率可能有極大的差異。在數據庫驅動(dòng)的項目里,有句話(huà)在項目前期的會(huì )議上經(jīng)常聽(tīng)到,我也常常拿來(lái)開(kāi)玩笑:
“我們已經(jīng)定好了數據庫Schema,現在可以開(kāi)始干活了!”
沒(méi)有取笑誰(shuí)的意思,我自己也說(shuō)過(guò)這樣的話(huà)。
雖然我有些猶豫要不要說(shuō)出來(lái),不過(guò)有的分塊也許的確能用那樣的方式取得很好的結果,定好數據庫Schema,以后就不再改了。
而同時(shí)其他的分塊可以采用DDD項目的典型風(fēng)格:
“好吧,我們現在了解得還不深,不過(guò)讓我們先按目前的理解來(lái)做,隨著(zhù)我們了解加深,領(lǐng)域模型可以天天改,也一定會(huì )天天改!”
現在兩種風(fēng)格可以愉快地并存。以前肯定也有并存的情況,只不過(guò)現在明確了界限,成功機會(huì )提高了。
有人說(shuō)任何值得討論的事物都應該賦予一個(gè)名稱(chēng),我當然不希望這種架構風(fēng)格僅僅因為沒(méi)有名稱(chēng)就失去討論的資格。
那么“分塊云計算(Chunk Cloud Computing)”是個(gè)好名稱(chēng)嗎?爭議肯定是少不了的,先放在一邊,聽(tīng)我說(shuō)一下來(lái)歷吧。
在本屆歐洲軟件架構研討會(huì )上,Christoffer Skjoldborg提了一個(gè)叫做“Chunk Cloud”的論題?!癈hunk”就是一小片(分塊),“Cloud”是現在的炒作題材。他的描述很接近我近來(lái)觀(guān)察到的一種應用架構方式。(本文就是努力在描述這種架構方式?!皬娖纫环N風(fēng)格”小節尤其得自Chris的啟發(fā)。)Chris描述的時(shí)候說(shuō)得有點(diǎn)極端,不過(guò)我想主要是為了讓他的觀(guān)點(diǎn)流傳開(kāi)。
最后的“Computing”是研討會(huì )上Nicklas Andersson(http://nickeandersson.blogs.com)加上去的,大概是因為CCC看著(zhù)比較酷吧。
不知道你怎么樣,我聽(tīng)到“最佳實(shí)踐”的時(shí)候會(huì )警覺(jué)起來(lái)??赡苁怯捎谀切┡cDreyfus模型[Dreyfus model of skill acquisition]相關(guān)的討論,還由于見(jiàn)識到一些無(wú)視上下文的“最佳實(shí)踐”……
我不認為“塊云計算”是一種最佳實(shí)踐。我只認為它是一種架構風(fēng)格,而我經(jīng)常發(fā)現適用這種風(fēng)格的場(chǎng)合。但別忘了,要看情況。
當然有了!可是你不覺(jué)得老問(wèn)題沒(méi)意思么?我知道這種風(fēng)格有很多新問(wèn)題,以后還會(huì )冒出更多(肯定的)。最多人提出來(lái)的兩個(gè)問(wèn)題是同樣的數據出現在好幾個(gè)地方,以及不同的分塊之間有數據不一致的風(fēng)險。這兩個(gè)問(wèn)題都必須解決,但請容我放到另一篇文章里。
還有一個(gè)經(jīng)常提出的問(wèn)題是“怎么做”,尤其是怎么將領(lǐng)域模型拆成幾個(gè)小塊。我會(huì )再找時(shí)間詳談。
更進(jìn)一步的觀(guān)察對此方向是肯定還是否定?
穩妥地說(shuō),兩方面的觀(guān)察(及觀(guān)點(diǎn))都有,我也期盼著(zhù)看到您的想法!
聯(lián)系客服