博爾赫斯說:“寫散文體的短文——寓言、神話、短故事——給了我某種神秘的滿足。想起這些篇章,就仿佛想到硬幣:實(shí)在、結(jié)實(shí)、閃光的小物體,更多的東西的樣品?!憋@然,小物體之美,讓博爾赫斯著迷。
同樣,在軟件設(shè)計領(lǐng)域里,小的設(shè)計同樣讓我著迷。這里所謂的“小”,并非絕對的小,而是強(qiáng)調(diào)一種恰如其分的設(shè)計哲學(xué)。在開發(fā)過程中,每一次迭代的目標(biāo)不宜設(shè)立過大,需小步前行,避免過度設(shè)計。在設(shè)計開發(fā)時,整個系統(tǒng)最好由松散耦合的細(xì)小模塊組成。這些細(xì)小模塊由于功能相對獨(dú)立而單一,因而更易于理解。

在設(shè)計系統(tǒng)架構(gòu)時,我們要注意克制做大做全的貪婪野心,盡力保證系統(tǒng)的小規(guī)模。Unix的締造者之一Dennis Ritchie就曾遭受過將系統(tǒng)做大做全的滑鐵盧。他在貝爾實(shí)驗(yàn)室的第一個任務(wù),是參與大項(xiàng)目Multics,即開發(fā)一個前所未有的、可以多人使用的、同時運(yùn)行多個程序的操作系統(tǒng)。該項(xiàng)目由貝爾實(shí)驗(yàn)室、麻省理工學(xué)院和通用電氣公司三方聯(lián)合研制,但是由于設(shè)計過于復(fù)雜,遲遲拿不出成果,1969年貝爾實(shí)驗(yàn)室宣布退出。
痛定思痛,Dennis Ritchie和同事Ken Thompson之后在設(shè)計Unix時,就吸取了Multics設(shè)計復(fù)雜而導(dǎo)致失敗的教訓(xùn),提出了"保持簡單和直接"(Keep it simple stupid)的原則,即所謂KISS原則。
遵循KISS原則,整個Unix系統(tǒng)由許多小程序組成,每個小程序只能完成一個功能,任何復(fù)雜的操作都必須分解成一些基本步驟,由這些小程序逐一完成,再組合起來得到最終結(jié)果。表面上看,運(yùn)行一連串小程序很低效。但是事實(shí)證明,由于小程序之間可以像積木一樣自由組合,所以非常靈活,能夠輕易完成大量意想不到的任務(wù)。而且,計算機(jī)硬件的升級速度非常快,所以性能也不是一個問題。另一方面,當(dāng)把大程序分解成單一目的的小程序,開發(fā)變得容易,Unix在短短幾個月內(nèi)就問世。
我們千萬不要輕視小的力量。
專注的小可以保證獨(dú)立進(jìn)化。
從“自治”思想看,它需要實(shí)現(xiàn)一定程度的自給自足,并保證對外交互的接口足夠穩(wěn)定。這符合老子的“大國小民”的思想。
這種專注的小首要在于分離職責(zé)。一種分離的角度是以“內(nèi)外之分”觀察核心與邊緣的職責(zé),然后以模塊分離的方式分別為它們尋找到“安身立命之所”。在框架設(shè)計上,為保證框架的小,我們常常采用這樣的觀察視角,這就是所謂的“內(nèi)核模式”,通過識別出整個框架的核心功能,以此作為整個框架的基礎(chǔ),而其余功能皆可視為框架的外圍功能,根據(jù)關(guān)注的域?qū)ζ溥M(jìn)行切分。這些外圍功能互相之間應(yīng)盡量減少耦合,使其能夠獨(dú)立進(jìn)化。

Spring框架的設(shè)計正是遵循了這樣的設(shè)計理念。除了Spring IoC是整個框架必備的組件之外,Spring MVC、Spring Batch Job、Spring Data等之間沒有依賴關(guān)系,可以根據(jù)項(xiàng)目自身情況酌情裁剪。
那么,如何才能保證設(shè)計的系統(tǒng)足夠小?首先,在設(shè)計思想上要確立“小即是美”的美學(xué)觀,要清晰地辨別且能夠欣賞小的靈活之美,完整之美與輕盈之美。只有在思想上認(rèn)同它,你才能順勢而為;只有從心理上感受到這種美麗,你才能響應(yīng)它的召喚。
靈活之美,在于它能快速地響應(yīng)變化,這種變化可能是局部的,也可以是整個設(shè)計方向的改變。例如,在多數(shù)企業(yè)系統(tǒng)和互聯(lián)網(wǎng)系統(tǒng)中,都需要分離Online和Offline任務(wù),以指定不同的架構(gòu)決策。又例如,我們可以設(shè)計獨(dú)立的、具有最小功能子集的Batch Job來承擔(dān)后臺任務(wù)。這些Batch Job可以作為一個單獨(dú)的應(yīng)用程序執(zhí)行在單獨(dú)的進(jìn)程中。一旦需求要求我們對設(shè)計做出改變,我們也能將修改控制在足夠小的范圍中,從而保證對整個系統(tǒng)不會帶來巨大的影響。
若遵循EDA(Event Driven Architecture)模式,我們可以根據(jù)業(yè)務(wù)領(lǐng)域的不同,設(shè)計出功能最小完整的自治組件。組件之間的通信通過事件來傳播,利用發(fā)布者/訂閱者的方式解除組件之間的耦合;又或者利用消息傳遞來處理業(yè)務(wù)邏輯,例如在AKKA中,我們可以設(shè)計出靈活而小的Actor對象;微服務(wù)(Micro Service)架構(gòu)則從服務(wù)級別展現(xiàn)了設(shè)計的靈活之美。
輕盈之美,體現(xiàn)在它的功能并不臃腫,對外部的依賴較少,既容易在系統(tǒng)中快速引入,又不會使原有系統(tǒng)變得笨重,還能很方便地部署或者啟動。
展現(xiàn)了輕盈之美的組件往往具有良好的可測試性。我們可以利用六邊形架構(gòu)將系統(tǒng)分隔為內(nèi)、外兩個邊界,凡是系統(tǒng)對外的通信,皆通過端口(Port)和適配器(Adapter)完成,這樣就能較好地解除對外部環(huán)境的依賴,提高系統(tǒng)的可測試性。而清晰的邊界劃分也是設(shè)計小組件的一種有效手段。

若對于框架或平臺而言,則需要盡力降低框架或平臺的侵入性。當(dāng)年Rod Jonson之所以提出J2EE Without EJB,正是因?yàn)镋JB的侵入性帶來了諸多病癥。當(dāng)然,從另一個角度來講,我們自己研發(fā)的產(chǎn)品或項(xiàng)目也要盡可能擺脫對外部資源的依賴,即所謂“穩(wěn)定依賴原則”。Robert Martin提出的Clean Architecture清晰地勾勒出這樣的思想。在Clean Architecture的表述中,他讓外部易變的部分依賴于更加穩(wěn)定的部分,如域模型,而非形成相反的依賴關(guān)系。這樣還可使實(shí)現(xiàn)變得更易于變化;多變的部分依賴于穩(wěn)定的部分。好架構(gòu)就要能輕松地改變那些易變的決定。

完整之美,在于它是自足的。完整并不意味著大而全,而在于它足夠精簡,沒有冗余。當(dāng)然,它同時應(yīng)該是沒有殘缺的。殘缺,意味著它無法在沒有外部支持的情況下,完成自己應(yīng)該完成的工作。這種美感符合“麻雀雖小,五臟俱全”的標(biāo)準(zhǔn)。Standalone的微服務(wù),正好體現(xiàn)了這種自容器的完整之美。
小的益處還有一點(diǎn),它可以使得我們在架構(gòu)決策或技術(shù)選型時,可以變得更加從容。
譬如說,因?yàn)槟承┰蛭覀冃枰獙⒄麄€企業(yè)系統(tǒng)(Monolithic架構(gòu))從WebLogic上遷移到JBoss上,無疑,這是一個艱難的決定,實(shí)施起來更是一個漫長的過程。如果系統(tǒng)是基于Micro Service的架構(gòu)風(fēng)格進(jìn)行構(gòu)建,每個服務(wù)根據(jù)各自情形選擇自己的技術(shù)棧。倘若需要對某些服務(wù)進(jìn)行技術(shù)棧遷移,相信這個問題不再變得棘手?!笙罂梢暂p盈地跳舞,但付出的努力會百倍于一只敏捷的狐貍。
如今,Java已經(jīng)發(fā)展到Java 8,引入的Lambda表達(dá)式等多個特性如此鮮嫩,讓人垂涎不已。然而據(jù)我所知,國內(nèi)多數(shù)企業(yè)的Java項(xiàng)目仍然停滯在JDK 6裹足不前。是JDK 8不夠好嗎?非也。蓋因?yàn)榍蠓€(wěn)的他們?nèi)匀恍拇骖檻]。即使Oracle號稱這種JDK的遷移多么的平滑,多么的穩(wěn)健,多數(shù)企業(yè)仍然不敢輕易做出遷移的決定。若因?yàn)檫w移而帶來未知的缺陷,可謂得不償失。既然現(xiàn)在項(xiàng)目運(yùn)行良好,何必冒此風(fēng)險。
于是,我們這個行業(yè)因?yàn)橄到y(tǒng)的龐大而變得守舊老成,亦步亦趨。并非大家沒有冒險的精神,實(shí)則是龐大的項(xiàng)目難以靈活地改變方向。倘若只是更新系統(tǒng)中的某一個庫或框架,形勢就截然不同了。記得在沒有l(wèi)ambda的時代,當(dāng)我們讓客戶看到了Guava的好處時,要引入Guava就輕而易舉,真如順?biāo)浦哿恕?/p>
當(dāng)我們發(fā)現(xiàn)某些功能具備獨(dú)立和專注的特征時,都是可能做出小系統(tǒng)的機(jī)會。這些小系統(tǒng)并不一定是子系統(tǒng)或模塊,它還可以是一個獨(dú)立的應(yīng)用或服務(wù)。
例如在一個稅務(wù)系統(tǒng)中,需要生成復(fù)雜的稅務(wù)報表。它的整個邏輯是相對獨(dú)立的,不管是報表的動態(tài)生成,格式的轉(zhuǎn)換,數(shù)據(jù)的查詢以及流的處理和PDF文檔的生成,都與系統(tǒng)其他部分關(guān)聯(lián)不大。唯一可能與系統(tǒng)存在緊密關(guān)聯(lián)的是數(shù)據(jù)庫。但為了解決高峰期的性能問題,我們可以建立單獨(dú)的數(shù)據(jù)提取器,又或引入流處理,定期將數(shù)據(jù)提取出來,放入內(nèi)存數(shù)據(jù)庫中。將這樣相對獨(dú)立的功能做成服務(wù),就能夠獨(dú)立演化,并有效支持服務(wù)請求的可伸縮。這樣的小型服務(wù)可以更靈活地應(yīng)對變化。當(dāng)我們發(fā)現(xiàn)內(nèi)存數(shù)據(jù)庫不能滿足大量請求時,也可以輕而易舉地將其遷移到NoSQL上,并根據(jù)數(shù)據(jù)的屬性例如按照地域進(jìn)行分區(qū),支持水平擴(kuò)展。
若要保證系統(tǒng)的小,我們還可以嘗試使用腳本。在開發(fā)軟件系統(tǒng)時,可以使用一些腳本語言來開發(fā)一些小工具,以應(yīng)對靈活的需求變化,消除重復(fù)代碼,實(shí)現(xiàn)某些步驟的自動化。例如用Groovy編寫一些函數(shù),用Ruby編寫代碼生成工具,又或者使用Gradle、SBT實(shí)現(xiàn)系統(tǒng)的自動部署,啟動服務(wù)器等腳本。腳本語言具有很好的靈活性,而動態(tài)語言的特性也使得我們能夠編寫出短小精悍的超級小工具,甚至可以作為系統(tǒng)模塊之間的粘合劑,如機(jī)器齒輪上的潤滑油一般,讓整個系統(tǒng)充滿活力。
Russ Miles認(rèn)為:團(tuán)隊(duì)的開發(fā)速度經(jīng)常因編寫代碼體積和復(fù)雜度的增長而放緩。他認(rèn)為組件結(jié)構(gòu)在簡化架構(gòu)方面非常重要,并提出了Life-Preserver模型。這是一個環(huán)形結(jié)構(gòu),所有的基礎(chǔ)設(shè)施軟件都在環(huán)上處理集成,而核心業(yè)務(wù)組件在環(huán)內(nèi)加入業(yè)務(wù)價值。這種模型的核心是利用事件來簡化架構(gòu)。
使用事件的一個顯著特點(diǎn)是解耦,使得事件的發(fā)布者與訂閱者都可以獨(dú)立演化,也可以自由增加事件的訂閱者數(shù)量。我們可以將事件的發(fā)布者與訂閱者設(shè)計為獨(dú)立的小程序,就像Unix系統(tǒng)中那些小工具一樣,通過管道建立關(guān)聯(lián)。這樣的設(shè)計思想稱之為EDA(Event Driven Architecture)。
當(dāng)然,要做到系統(tǒng)的“小”,必然也是要付出代價的。奧卡姆剃刀定律認(rèn)為:“若無必要,勿增實(shí)體”。剖析Kent Beck提出的“簡單設(shè)計”原則,在滿足了客戶功能、無多余重復(fù)、清晰表達(dá)設(shè)計意圖的前提下,需要遵循奧卡姆剃刀定律。蓋因?yàn)橄到y(tǒng)分解得約小,就會因?yàn)椤皩?shí)體”數(shù)量的增加,引入額外的復(fù)雜度,包括對實(shí)體的管理、實(shí)體之間的協(xié)作等。顯然,任何設(shè)計決策都有其兩面性,我們需要放入到當(dāng)下的上下文(Context)中做出正確的判斷。正如愛因斯坦所說:“讓它盡可能簡單,但不要過于簡單?!笨磥恚覀儗θ魏问虑槎夹枰盐找粋€“度”,水滿則溢,月盈則虧,故而需要損有余而補(bǔ)不足。