遠在2009年,Martin Fowler與Rebecca Parsons在QCon SF做了一次題為Agilists and Architects: Allies not Adversaries Presentation的演講。演講主要討論了在敏捷方法中的架構活動。相似的話題,Neal Ford則提出了緊急設計的概念,并發表了名為Evelutionary Architecture and Emergent Design(演進架構與緊急設計)的系列文章。這是很棒的一個講解演進架構的系列文章,談到了TDD、代碼復用、連貫接口、DSL、重構、慣用法模式、指標等與演進架構和緊急設計有關的內容。
Neal Ford對軟件架構的主要觀點基于如下事實:
- 未來是不可預測的;
- 軟件設計的最終目標是獲得完整的源代碼;
- 系統的復雜度分為偶發復雜度(accidental complexity)與本質復雜度(essential complexity);
于是,我們應該選擇在最后責任時刻(Last Responsible Moment)去應對系統的復雜度。所謂“最后責任時刻”,即我們如果未及時采取措施,可能導致復雜度線性增加的時刻,如下圖所示:
Ford提出的方法就是在開發中善于發現抽象與模式,并借助測試驅動開發,利用重構去導向設計。同時,我們還可以嘗試使用一些考量代碼質量的工具,獲得質量指標,通過這些指標去發現問題(這些問題其實就是技術債),然后去及時解決問題。針對較難做出的架構決策,則可以利用Spike方式快速得出結論,甚至是原型方案。
事實上,演進式架構這個話題已經是老調重彈。讓我們再回到2004年,Martin Fowler當然發表了文章Is Design Dead。文中談到了計劃式設計與演進式設計之間的區別。這篇文章算得上是溯本清源。在2007年我自己的書《軟件設計精要與模式》中,也簡單闡述了我對二者的理解。我給出了一個建筑學的隱喻:拙政園與周莊。拙政園是計劃式設計的典范,沒有詳盡的計劃,也許就不會有疏朗典雅的拙政園。周莊卻并非某人在某一時刻靈感捕捉后的設計成果,而是經歷了數百年的歷史滄桑,漸進地增添與更替各種建筑,最后形成現在這般靈秀的水鄉風貌。我在書中寫道:
演進的設計,同樣需要遵循架構設計的基本準則,它與計劃的設計唯一的區別是設計的目標。演進的設計提倡滿足客戶現有的需求;而計劃的設計則需要考慮未來的功能擴展。演進的設計推崇盡快地實現,追求快速確定解決方案,快速編碼以及快速實現;而計劃的設計則需要考慮計劃的周密性,架構的完整性并保證開發過程的有條不紊。
2010年,我翻譯了George Fairbanks的著作Just Enough Software Architecture。
書中除了計劃式設計和演進式設計之外,還提到了第三種設計:Minimal planned design(最小計劃設計),這算是一種中庸之道的選擇。書中認為,演進式設計需要與一些敏捷實踐配合,包括重構、測試驅動設計與持續集成。George認為計劃式設計背后隱藏的思想是在構造開始之前,制訂的計劃可以設計出很好的細節。他還提到:
當架構為并行的多個團隊所共享時,計劃式架構設計就具有實踐意義,在子團隊開始工作之前,這種計劃式設計頗為有效。
書中還寫道:(對于多團隊開發而言)計劃式架構定義了高層的組件與連接器,并與局部的設計相匹配,而子團隊則設計這些組件與連接器的內部模型。架構常常會保證整體的不變量與設計決策,例如建立并發策略、連接器的標準集、分配高層職責或定義某些局部的質量屬性場景。
最小計劃設計,則介乎于演進式設計與計劃式設計之間。支持這種設計的人認為:如果完全采取演進式設計,可能會使得設計走向死胡同;而計劃式設計又非常難,因為事先對系統并沒有全面的了解,可能導致設計錯誤。在2002年Bill Venners對Martin Fowler的采訪中,Martin Fowler認為,最合理的分配是20%的計劃式設計,80%的演進式設計。在George的書中,作者認為需要權衡計劃式與演進式設計。一種做法是在項目初期進行計劃式設計,確保架構能夠處理最大的風險。之后,就可以通過局部的設計來應對需求的變化,或者采用演進式設計,通過推行重構、測試驅動設計與持續集成對架構進行演化。
博客coding the architecture上的一篇文章Just enough architecture,從方法學的角度分析如何獲得恰如其分的架構。
文章以及上圖所表達出來的含義是:傳統的瀑布式采取事先設計的做法,可以認為是計劃式設計;敏捷方法學傾向于演進式設計;處于其中的RUP則更像是前面提到的最小計劃設計。文中主要還是關注我們在架構過程中如何做到架構的“just enough”。事實上,這一觀點在George Fairbanks的著作Just enough software architecture中被反復提到,要做到這一點,就需要采用風險驅動模型(Risk-Driven Model)。RDM的架構步驟分為三步:
- 識別風險并進行優先級排列
- 選擇并應用相關技術
- 評估風險是否降低
其實風險驅動模型的三個步驟很容易理解,關鍵是我們應該如何識別風險,如何排列優先級,又該如何確定解決或控制風險的技術,并進行合理地評估,這是風險驅動模型的難點。我認為RDM帶來的益處在于它給出了一個非常具有實踐意義的驅動原則與方法,它告訴架構師,當我們在對系統進行架構時,需要從一開始就要重視風險,例如系統的安全性、可伸縮性、安全等諸多與質量屬性有關的技術風險。
整體而言,這三種方式的設計各有優劣,我們應根據具體的場景,具體的項目,具體的團隊進行針對性地分析。應該把握“因地制宜”的原則,認識到不同的項目需要不同的設計方式。對于不同的開發團隊,做出的選擇也會不同。例如,如果開發團隊精于重構、測試驅動設計,并能很好地實施持續集成,就可以考慮采用演進式設計或最小計劃設計。
我個人較傾向于Minimal planned design,至于它在演進式設計與計劃式設計之前的權衡,不必完全照搬Martin Fowler給出的比例。參考DDD的分類,我將計劃式設計的部分規劃到戰略式設計中,此時,我可以從用例出發,引入Bounded Context來尋找系統的核心領域與子領域。
通過Context Map并結合六邊形架構,可以幫助我們識別Context或者說領域之間的通信方式與集成方式,從而獲得整個系統的分布式架構模型。運用分層架構以及六邊形架構驅動得出的Port與Adapter,可以幫助我們獲得整個系統的應用邏輯架構。而Context自身,則可以作為業務邏輯架構的基礎。
軟件系統的質量屬性算是特殊的一部分,可以借鑒質量驅動設計或風險驅動設計,來確定滿足質量屬性的架構方案。在這個過程中,我們可以參考常用的架構風格與架構模式。例如針對大數據處理、并發處理、資源管理、分布式架構,都有許多相應的模式與風格可供我們選擇。架構風格與架構模式的選擇,會直接影響到我們的系統架構。
當我們分別有了物理架構、應用邏輯架構與業務邏輯架構之后,計劃式設計的過程就可以畫一個句號了。由于我們有了Context作為領域邊界,使得我們能夠更好地劃分特性團隊。如DDD所述,團隊之間的關系可以參考Context Map,例如Partnership、Consumer-Provider等。之后的過程就進入了DDD的戰術設計層面。在這個層次,我們可以結合團隊成員的能力來選擇不同的設計方法。例如,可以選擇DDD方法繼續對子領域進行領域建模;也可以從Application層面,通過用例驅動設計,結合CRC卡和時序圖進一步細化;當然,也可以通過ATDD與TDD進行測試驅動。
無論選擇何種方式,我們的設計都應該把握“恰如其分”這個原則,不做不必要的“過度工程”。這或者可以看做敏捷架構的中心原則。