架構(gòu)師必須處理軟件項目所有不同方面的各種架構(gòu)特征。 諸如性能、彈性和可伸縮性之類的運維方面與諸如模塊化和可部署性之類的結(jié)構(gòu)性問題融合在一起。這里著重于具體定義一些較常見的架構(gòu)特征并為其建立治理機制。
測量架構(gòu)特征
組織中有關(guān)架構(gòu)特征的定義存在幾個常見問題:
他們不是物理學(xué)
常用的許多架構(gòu)特征含義不明確。例如,架構(gòu)師如何設(shè)計敏捷性或可部署性?業(yè)界對通用術(shù)語的看法大相徑庭,有時是由合理的不同上下文所驅(qū)動,有時是偶然的。
定義千差萬別
即使在同一組織內(nèi),不同部門也可能在關(guān)鍵功能(例如績效)的定義上存在分歧。在開發(fā)人員,架構(gòu)和運維可以統(tǒng)一一個共同的定義之前,要進行適當(dāng)?shù)膶υ捑秃芾щy。
太復(fù)合
許多所需的架構(gòu)特征在較小規(guī)模上包含許多其他特征。例如,開發(fā)人員可以將敏捷性分解為諸如模塊化、可部署性和可測試性之類的特征。
架構(gòu)特征的客觀定義解決了所有三個問題:通過在組織范圍內(nèi)就架構(gòu)特征的具體定義達成一致,團隊圍繞架構(gòu)創(chuàng)建了一種普遍存在的語言。同樣,通過鼓勵客觀定義,團隊可以解開組合特征以發(fā)現(xiàn)他們可以客觀定義的可測量特征。
可操作的測量
許多架構(gòu)特征具有明顯的直接度量,例如性能或可伸縮性。但是,即使根據(jù)團隊的目標,這些也提供了許多細微的解釋。例如,也許一個團隊測量了某些請求的平均響應(yīng)時間,這是運維架構(gòu)特性測量的一個很好的例子。但是,如果團隊僅測量平均值,那么如果某些邊界條件導(dǎo)致1%的請求花費的時間比其他時間長10倍,會發(fā)生什么情況?如果該站點有足夠的流量,則異常值甚至可能不會出現(xiàn)。因此,團隊可能還需要測量最大響應(yīng)時間以捕獲異常值。
高水平的團隊不只是建立出色的性能數(shù)字; 他們的定義基于統(tǒng)計分析。 例如,假設(shè)視頻流服務(wù)希望監(jiān)視可伸縮性。 工程師沒有設(shè)置任意數(shù)字作為目標,而是隨著時間的推移測量規(guī)模并建立統(tǒng)計模型,然后在實時指標超出預(yù)測模型時發(fā)出警報。 失敗可能意味著兩件事:模型不正確(哪些團隊想知道)或某事不正確(哪些團隊也想知道)。
結(jié)合工具和細致入微的理解,團隊現(xiàn)在可以衡量的各種特征正在迅速發(fā)展。 例如,許多團隊最近將精力集中在性能預(yù)算上,以衡量指標,例如第一幅有意義的圖表和第一次發(fā)生的CPU空閑,這兩者都為移動設(shè)備上的網(wǎng)頁用戶指出了性能問題。 隨著設(shè)備、目標、功能和其他眾多事物的變化,團隊將找到新的事物和測量方法。
結(jié)構(gòu)化測量
一些客觀指標并不像績效指標那么明顯。 內(nèi)部結(jié)構(gòu)特征如何,例如定義明確的模塊化呢? 遺憾的是,還沒有用于內(nèi)部代碼質(zhì)量的綜合指標。 但是,某些度量標準和通用工具的確可以使架構(gòu)師解決代碼結(jié)構(gòu)的某些關(guān)鍵方面,盡管范圍狹窄。
代碼的一個明顯的可測量方面是復(fù)雜度,由循環(huán)復(fù)雜度度量定義。
架構(gòu)師和開發(fā)人員普遍同意,過于復(fù)雜的代碼代表著代碼臭味(Code Smell)。 它實際上損害了代碼庫的每個令人希望的特性:模塊化、可測試性、可部署性等。 但是,如果團隊不關(guān)注逐漸增加的復(fù)雜性,那么這種復(fù)雜性將主導(dǎo)代碼庫。
過程測量
一些架構(gòu)特征與軟件開發(fā)過程相交。例如,敏捷性經(jīng)常表現(xiàn)為理想的功能。但是,這是架構(gòu)師可以分解為可測試性和可部署性等功能的復(fù)合架構(gòu)特征。
可通過幾乎所有評估測試完整性的平臺的代碼覆蓋率工具來衡量可測試性。像所有軟件檢查一樣,它不能代替思想和意圖。例如,一個代碼庫可以具有100%的代碼覆蓋率,但是斷言不佳,實際上并不能使人們對代碼的正確性充滿信心。但是,可測試性顯然是客觀可測量的特征。同樣,團隊可以通過多種指標來衡量可部署性:成功部署與失敗部署的百分比,部署需要花費多長時間,部署引發(fā)的問題/錯誤以及許多其他問題。每個團隊都有責(zé)任進行一組良好的評估,以捕獲其組織在質(zhì)量和數(shù)量上有用的數(shù)據(jù)。其中許多措施歸結(jié)為團隊的重點和目標。
敏捷及其相關(guān)部分顯然與軟件開發(fā)過程有關(guān)。但是,該過程可能會影響架構(gòu)。例如,如果易于部署和可測試性是重中之重,那么架構(gòu)師將在架構(gòu)級別更加注重良好的模塊化和隔離性,這是驅(qū)動結(jié)構(gòu)決策的架構(gòu)特征示例。實際上,如果軟件項目范圍內(nèi)的任何事情都能夠滿足我們的三個標準,那么它就可能會升至架構(gòu)特征的水平,從而迫使架構(gòu)師做出設(shè)計決定以考慮到這一點。
治理和適應(yīng)功能
一旦架構(gòu)師確定了架構(gòu)特征并對其進行了優(yōu)先排序,他們?nèi)绾未_保開發(fā)人員將尊重這些優(yōu)先事項?模塊化是架構(gòu)方面的一個重要例子,它很重要但并不緊迫。在許多軟件項目中,緊迫性占主導(dǎo)地位,但是架構(gòu)師仍然需要一種治理機制。
主導(dǎo)架構(gòu)特征
源自希臘語Kubernan(轉(zhuǎn)向)的治理是架構(gòu)師角色的重要職責(zé)。顧名思義,架構(gòu)治理的范圍涵蓋了架構(gòu)師(包括企業(yè)架構(gòu)師之類的角色)想要施加影響的軟件開發(fā)過程的任何方面。例如,確保組織內(nèi)的軟件質(zhì)量屬于架構(gòu)治理的范疇,因為它屬于架構(gòu)的范圍,而過失可能導(dǎo)致災(zāi)難性的質(zhì)量問題。
幸運的是,存在越來越復(fù)雜的解決方案,可以減輕架構(gòu)師的困擾,這是軟件開發(fā)生態(tài)系統(tǒng)中功能不斷增長的一個很好的例子。極限編程催生的軟件項目實現(xiàn)自動化,實現(xiàn)了持續(xù)集成,進一步實現(xiàn)了操作的自動化,現(xiàn)在我們將其稱為DevOps,一直持續(xù)到架構(gòu)治理。 《構(gòu)建演化架構(gòu)》(O’Reilly)一書描述了稱為適應(yīng)度函數(shù)的一系列技術(shù),這些技術(shù)可用于自動執(zhí)行架構(gòu)管理的許多方面。
適應(yīng)度函數(shù)
構(gòu)建演化架構(gòu)中的“演化”一詞更多地來自于演化計算,而不是生物學(xué)。其中一位作者Rebecca Parsons博士在演化計算領(lǐng)域花費了一些時間,其中包括遺傳算法之類的工具。遺傳算法執(zhí)行并產(chǎn)生答案,然后通過進化計算世界中定義的眾所周知的技術(shù)進行變異。如果開發(fā)人員試圖設(shè)計一種遺傳算法以產(chǎn)生一些有益的結(jié)果,則他們通常希望對算法進行指導(dǎo),從而提供指示結(jié)果質(zhì)量的客觀指標。該指導(dǎo)機制稱為適應(yīng)度函數(shù):一種目標函數(shù),用于評估輸出與達到目標的接近程度。例如,假設(shè)開發(fā)人員需要解決移動營業(yè)員問題,這是一個著名的問題,是機器學(xué)習(xí)的基礎(chǔ)。給定一個銷售員和他們必須訪問的城市列表,以及它們之間的距離,最佳路線是什么?如果開發(fā)人員設(shè)計了一種遺傳算法來解決此問題,則一個適應(yīng)度函數(shù)可能會評估路線的長度,因為最短的可能代表最大的成功。另一個適應(yīng)性功能可能是評估與路線相關(guān)的總成本,并嘗試將成本保持在最低水平。另一個可能是評估移動銷售人員離開的時間,并進行優(yōu)化以縮短總旅行時間。
演化架構(gòu)的實踐借用了這個概念來創(chuàng)建架構(gòu)適應(yīng)性功能:
架構(gòu)適應(yīng)度函數(shù)
提供對某些架構(gòu)特征或架構(gòu)特征組合進行客觀完整性評估的任何機制
適應(yīng)度函數(shù)不是供架構(gòu)師下載的某些新框架,而是對許多現(xiàn)有工具的新視角。注意定義中的“任何機制”一詞-用于架構(gòu)特征的驗證技術(shù)隨其特征而變化。適應(yīng)度函數(shù)與許多現(xiàn)有的驗證機制重疊,具體取決于它們的使用方式:作為度量標準、監(jiān)控、單元測試庫、混亂工程等等,如圖所示。

根據(jù)架構(gòu)特征,可以使用許多不同的工具來實現(xiàn)適應(yīng)度函數(shù)。例如,在“耦合”中,我們引入了度量標準,以允許架構(gòu)師評估模塊性。以下是適合度函數(shù)的幾個示例,它們可以測試模塊化的各個方面。
循環(huán)依賴
模塊化是大多數(shù)架構(gòu)師關(guān)心的隱式架構(gòu)特征,因為維護不當(dāng)?shù)哪K化會損害代碼庫的結(jié)構(gòu)。因此,架構(gòu)師應(yīng)高度重視保持良好的模塊化。但是,在許多平臺上,團隊與架構(gòu)師的良好意圖背道而馳。例如,在任何流行的Java或.NET開發(fā)環(huán)境中進行編碼時,一旦開發(fā)人員引用了尚未導(dǎo)入的類,IDE就會幫助顯示一個對話框,詢問開發(fā)人員是否要自動導(dǎo)入該引用。這種情況經(jīng)常發(fā)生,以至于大多數(shù)程序員習(xí)慣于像反射動作一樣將自動導(dǎo)入對話框拖走。但是,任意地在彼此之間導(dǎo)入類或組件對于模塊化來說是災(zāi)難。例如,圖展示了一種架構(gòu)師渴望避免的特別有害的反模式。

在圖中,每個組件都引用了其他組件。 擁有這樣的組件網(wǎng)絡(luò)會破壞模塊化,因為開發(fā)人員無法重用單個組件,而又不能使其他組件一起使用。 而且,當(dāng)然,如果將其他組件耦合到其他組件,則該架構(gòu)將越來越傾向于“大泥巴”反模式。 架構(gòu)師如何控制這種行為,而又不需時不時地看著那些對觸發(fā)器滿意的開發(fā)人員? 代碼審查雖然有幫助,但在開發(fā)周期中為時已晚,無法生效。 如果架構(gòu)師允許開發(fā)團隊在整個代碼庫中導(dǎo)入直到進行代碼審查,則代碼庫中已經(jīng)發(fā)生了嚴重損壞。
解決此問題的方法是編寫一個適應(yīng)度函數(shù)以查看周期,如示例所示。
示例 適應(yīng)度函數(shù)可檢測零件循環(huán)
public class CycleTest {
private JDepend jdepend;
@BeforeEach
void init() {
jdepend = new JDepend();
jdepend.addDirectory("/path/to/project/persistence/classes");
jdepend.addDirectory("/path/to/project/web/classes");
jdepend.addDirectory("/path/to/project/thirdpartyjars");
}
@Test
void testAllPackages() {
Collection packages = jdepend.analyze();
assertEquals("Cycles exist", false, jdepend.containsCycles());
}
}
在代碼中,架構(gòu)師使用指標工具JDepend來檢查包之間的依賴關(guān)系。 該工具了解Java包的結(jié)構(gòu),如果存在任何循環(huán),則測試失敗。 架構(gòu)師可以將此測試連接到項目的連續(xù)構(gòu)建中,而不必擔(dān)心觸發(fā)滿意的開發(fā)人員意外引入周期。 這是一個適應(yīng)度函數(shù),可以保護重要而不是緊迫的軟件開發(fā)實踐的一個很好的例子:這是對架構(gòu)師的重要關(guān)注,但對日常編碼幾乎沒有影響。
與主要序列適應(yīng)度函數(shù)的距離
在“耦合”中,我們引入了距主序列更深奧的距離度量,架構(gòu)師也可以使用適應(yīng)度函數(shù)進行驗證,如示例所示。
示例—與主序列適應(yīng)度函數(shù)的距離
@Test
void AllPackages() {
double ideal = 0.0;
double tolerance = 0.5; // project-dependent
Collection packages = jdepend.analyze();
Iterator iter = packages.iterator();
while (iter.hasNext()) {
JavaPackage p = (JavaPackage)iter.next();
assertEquals("Distance exceeded: " + p.getName(),
ideal, p.distance(), tolerance);
}
}
在代碼中,架構(gòu)師使用JDepend為可接受的值建立閾值,如果類超出范圍,則測試失敗。
這既是針對架構(gòu)特性的客觀度量的示例,又是設(shè)計和實現(xiàn)適應(yīng)性功能時開發(fā)人員與架構(gòu)師之間協(xié)作的重要性的示例。這樣做的目的不是讓一群架構(gòu)師登上象牙塔并開發(fā)開發(fā)人員無法理解的深奧適應(yīng)度函數(shù)。
提示
架構(gòu)師必須確保開發(fā)人員在將適應(yīng)度函數(shù)強加給他們之前了解其功能。
在過去的幾年中,適應(yīng)度函數(shù)工具(包括一些專用工具)的復(fù)雜性有所提高。一個這樣的工具就是ArchUnit,這是一個Java測試框架,其靈感來自于JUnit生態(tài)系統(tǒng)的各個部分并使用了這些部分。 ArchUnit提供了各種預(yù)定義的管理規(guī)則,這些規(guī)則被編碼為單元測試,并允許架構(gòu)師編寫解決模塊化的特定測試??紤]圖中所示的分層架構(gòu)。

設(shè)計分層整體結(jié)構(gòu)(例如圖6-4中的整體結(jié)構(gòu))時,架構(gòu)師有充分的理由定義層(動機,權(quán)衡和分層體系結(jié)構(gòu)的其他方面在第10章中進行了描述)。 但是,架構(gòu)師如何確保開發(fā)人員會尊重這些層? 一些開發(fā)人員可能不了解這些模式的重要性,而其他開發(fā)人員則可能會采用“更好地請求寬恕而不是允許”的態(tài)度,這是因為一些諸如性能之類的對本地問題的關(guān)注。 但是,允許實施者侵蝕架構(gòu)的原因會損害架構(gòu)的長期健康。
ArchUnit允許架構(gòu)師通過適應(yīng)性函數(shù)解決此問題,如示例所示。
示例—ArchUnit適應(yīng)度函數(shù)可控制圖層
layeredArchitecture()
.layer("Controller").definedBy("..controller..")
.layer("Service").definedBy("..service..")
.layer("Persistence").definedBy("..persistence..")
.whereLayer("Controller").mayNotBeAccessedByAnyLayer()
.whereLayer("Service").mayOnlyBeAccessedByLayers("Controller")
.whereLayer("Persistence").mayOnlyBeAccessedByLayers("Service")
在例中,架構(gòu)師定義了各層之間的理想關(guān)系,并編寫了驗證適應(yīng)度函數(shù)來對其進行控制。
.NET空間中的類似工具NetArchTest允許對該平臺進行類似的測試。 例中顯示了C#中的依賴驗證。
示例—NetArchTest用于層依賴性
// Classes in the presentation should not directly reference repositories
var result = Types.InCurrentDomain()
.That()
.ResideInNamespace("NetArchTest.SampleLibrary.Presentation")
.ShouldNot()
.HaveDependencyOn("NetArchTest.SampleLibrary.Data")
.GetResult()
.IsSuccessful;
適應(yīng)度函數(shù)的另一個例子來自Netflix的混亂的猴子和猴子部隊。 整合安全性和管理猴子就是這種方法的例證。 從中猴子允許Netflix的架構(gòu)師定義生產(chǎn)中由猴子執(zhí)行的治理規(guī)則。 例如,如果架構(gòu)師決定每個服務(wù)都應(yīng)該對所有RESTful動詞做出有用的響應(yīng),那么他們會將檢查內(nèi)容構(gòu)建到從眾猴子中。 同樣,安全猴子會檢查每個服務(wù)是否存在眾所周知的安全缺陷,例如不應(yīng)激活的端口和配置錯誤。 最終,看門人猴子尋找不再有其他服務(wù)路由到的實例。 Netflix具有不斷發(fā)展的架構(gòu),因此開發(fā)人員通常會遷移到較新的服務(wù),而無需協(xié)作者即可運行舊服務(wù)。 由于在云上運行的服務(wù)會消耗金錢,因此看門人猴子會尋找孤立的服務(wù)并將其分解到生產(chǎn)環(huán)境之外。
幾年前,Atul Gawande撰寫的頗有影響力的書《清單宣言》描述了航空飛行員和外科醫(yī)生等職業(yè)如何使用清單(有時是法律規(guī)定的)。 這不是因為這些專業(yè)人士不了解工作或健忘。 相反,當(dāng)專業(yè)人士一遍又一遍地完成非常詳細的工作時,細節(jié)容易流失。 簡潔的清單可以有效地提醒您。 這是適應(yīng)度函數(shù)的正確視角—適應(yīng)度函數(shù)不是重量級的治理機制,而是為架構(gòu)師提供了一種機制,可以表達重要的架構(gòu)原理并自動進行驗證。 開發(fā)人員知道他們不應(yīng)該發(fā)布不安全的代碼,但是對于繁忙的開發(fā)人員來說,優(yōu)先級會與數(shù)十或數(shù)百個其他優(yōu)先級競爭。 特別是像安全猴子這樣的工具,以及一般的適應(yīng)度函數(shù),允許架構(gòu)師將重要的治理檢查編入架構(gòu)的基礎(chǔ)。