是時候進入軟體架構了。在之前已經介紹了模組/類、元件的相關內容,這一部分會重點介紹軟體架構的基本定義。
寫在最前面
🌟軟體架構師自身需要是程式設計師,並且必須一直堅持做一線程式設計師,如果不親身承受因系統設計而帶來的麻煩,就體會不到設計不佳所帶來的痛苦,接著就會逐漸迷失正確的設計方向。
什麼是軟體架構?
軟體架構工作的實質就是討論規劃如何將系統切分成元件,並安排好元件之間的排列關係與通訊方式,即確定邊界。
軟體架構設計的目的一般有兩個:
- 為了在工作中更好的對這些元件進行研發、部署、執行以及維護;
- 如果想設計一個便於推進各項工作的系統,其策略就是要在設計中儘可能長時間地保留儘可能多的可選項;
一個軟體系統的架構質量和該系統是否能正常工作(行為)的關係並不大,畢竟世界上有很多架構設計糟糕但是工作正常的軟體系統。真正的麻煩往往會出現在這個軟體系統的開發、部署以及後續的補充開發中。這也間接反映的作者的觀點:軟體系統的架構價值在一定程度上大於其行為價值。
軟體架構設計的目標如下:
- 核心目標:一個良好的架構設計應該圍繞著用例來展開,良好的架構設計應該只關注用例,並能將它們與其他的周邊因素隔離,隔離的方式是儘可能推遲選擇,保留更多的可選項;
- 主要目標:支撐軟體系統的全生命週期,設計良好的架構可以讓系統便於理解、易於修改、方便維護,並且能輕鬆部署;
- 終極目標:最大化程式設計師的生產力(解放生產力),同時最小化系統的總運營成本;
軟體架構的設計重點是將策略彼此分離,然後將它們按照變更的方式進行重新分組,也就是劃分出邊界,在這一部分可以依據的原則包括SRP、CCP、CSP,SDP、SAP等等。
軟體架構的職責
軟體架構具有開發、部署、執行以及維護多個方面的職責。
在開發上:軟體架構要方便軟體系統的開發,因此不同的團隊應該使用不同的軟體架構設計。這在一定程度上也體現了康威定律。
為什麼康維定律能夠體現團隊的架構?看影片瞭解更多:康威定律:為什麼你的架構會反映團隊結構?_嗶哩嗶哩_bilibili
在部署上:軟體架構要實現軟體的一鍵式輕鬆部署;
在執行上,
- 正如前面提到的,架構對於軟體系統的高效執行遠小於其他幾個方面,作者認為這主要源於增加硬體資源可以彌補架構在執行方面的考慮不足,而架構設計通常需要更為昂貴的人力資源;
- 除了架構設計對於高效執行的作用,架構還應該能反應系統在執行時的需求,也就是說,設計良好的系統架構應該可以使開發人員對系統的執行過程一目瞭然。架構應該起到揭示系統執行過程的作用,架構應該將系統中的用例、功能以及該系統的必備行為設定為對開發者可見的一級實體,簡化它們對於系統的理解,《尖叫的軟體架構》一章就是在重點說明這個問題;
在維護上,軟體系統維護的成本一般是最高的,維護的成本可以分為探秘與風險兩類:
- 探秘:探秘(spelunking)的成本主要來自我們對於現有軟體系統的挖掘,目的是確定新增功能或被修復問題的最佳位置和最佳方式;
- 風險:風險(risk),則是指當我們進行上述修改時,總是有可能衍生出新的問題,這種可能性就是風險成本;
保持可選項
回憶之前提到的軟體架構提到的兩種價值:“行為價值”和“架構價值”,提升架構價值的方式就是讓軟體更軟,而讓軟體更軟的方式就是讓軟體架構儘可能多的保持可選項。
軟體系統的元素分為策略與細節兩類,策略體現的是軟體中所有的業務規則與操作過程,因此它是系統的真正價值所在,細節則是指那些讓操作該系統的人、其他系統以及程式設計師們與策略進行互動,但是又不會影響到策略本身的行為。如 IO 裝置、資料庫等等,細節選項應該是儘可能保留的。
軟體架構師的目標就是要建立一種系統形態,該形態會以策略為最基本的元素,並讓細節與策略脫離關係,以允許在具體決策過程中推遲或延遲與細節相關的內容。因為越到專案的後期,我們就擁有越多的資訊來做出合理的決策。一個優秀的軟體架構師應該致力於最大化可選項的數量。
作者使用裝置無關性的例子來說明推遲細節的重要性。現代作業系統的輸入輸出裝置種類多樣,而在計算機發展的最初時期,打孔紙帶是主流(幾乎是唯一)的一種輸入輸出方式,程式設計人員很自然的就將讀取/輸出紙帶的程式碼耦合到了系統程式碼當中,當磁帶出現後,開發人員不得不開發新的程式碼來適配磁帶輸入輸出,光碟出現後…為了應對這種重複開發,適應性差的問題,開發人員提出了裝置無關行的概念,即將裝置抽象成函式,OS會利用函式與輸入輸出裝置互動,而開發人員只需要提供這些函式的具體實現。此時輸入輸出就成為了OS的外掛,這也是開閉原則的雛形(擁抱👏新增、抗拒🥊修改!)。
保持獨立性
良好的軟體架構應該有充足的獨立性。
解耦可以讓我們保證軟體架構的獨立性。
解耦分為水平解耦與垂直解耦。
- 水平解耦(按層解耦):系統可以被解耦成若干個水平分層——UI、資料庫等等;
- 用例解耦(垂直解耦):在水平分層解耦的同時,也按用例將其切分成多個垂直分片,如:將增加訂單的用例UI與刪除訂單的用例UI分開。
如果我們按照變更原因的不同對系統進行解耦,就可以持續地向系統內新增新的用例,而不會影響舊有的用例。
同樣,解耦的層次也可以是不同的。可以在原始碼、部署以及服務三個不同的層次來完成解耦。
- 原始碼層次:控制原始碼中模組之間的依賴關係,系統的元件通過函式呼叫來進行互動。這種模式叫做單體結構;
- 部署層次:控制部署單元(如 Jar 檔案)之間的依賴關係,系統的元件可能使用跨執行緒(注意不是跨網路)的通訊、Socket 通訊或共享記憶體融通訊;
- 服務層次:將元件間的依賴降低到資料結構級別,然後通過網路資料包進行通訊,如微服務通過 rpc、rest 互動。
並沒有嚴格的標準說明那個解耦層次是更好的,因為隨著專案的逐漸成熟,最好的解耦模式可能會發生變化。一個設計良好的架構應該能允許一個系統從單體結構開始,以單一檔案的形式部署,然後逐漸成長為一組相互獨立的可部署單元,甚至是獨立的服務或者微服務。最後還能隨著情況的變化,允許系統逐漸回退到單體結構。一個設計良好的架構在上述過程中還應該能保護系統的大部分原始碼不受變更影響。對整個系統來說,解耦模式也應該是一個可選項。我們在進行大型部署時可以採用一種模式,而在進行小型部署時則可以採用另一種模式。
在瞭解解耦之後,讓我們看看解耦對系統獨立性到底有何影響。
解耦對系統執行獨立性的意義
如果不同面向之間的用例得到了良好的隔離,那麼需要高吞吐量的用例就和需要低吞吐量的用例互相自然分開了。如果 UI 和資料庫的部分能從業務邏輯分離出來,那麼它們就可以執行在不同的伺服器上。而且需要較大頻寬的應用也可以在多個伺服器上執行多個例項。
解耦對系統開發獨立性的意義
只要系統按照其水平分層和用例進行了恰當的解耦,整個系統的架構就可以支援多團隊開發,不管團隊組織形式是分功能開發、分元件開發、分層開發,還是按照別的什麼變數分工都可以
解耦對系統部署獨立性的意義
如果解耦工作做得好,我們甚至可以在系統執行過程中熱切換(hot-swap)其各個分層實現和具體用例。在這種情況下,我們增加新用例就只需要在系統中新增一些新的jar檔案,或啟動一些服務即可,其他部分將完全不受影響
什麼是程式碼重複
程式碼中的重複可以分為兩種:
- 真正的重複:程式碼中需要被消滅的部分;
- 虛假的重複:看起來重複的程式碼,實際上是不同的演進路徑,也就是擁有不同的變更速率與變更邊緣。CRP闡述:對於使用頻次不同的程式碼需要拆分到不同的元件中,如果在閱讀程式碼時沒有考慮到這一點就很有可能誤認為虛假的重複為重複程式碼,這些“重複”有時候是必須的,還記得計算工資的例子嗎?
(第五篇完)