在某些時(shí)候,工程師必須繪制一些方框和箭頭來(lái)描述軟件系統(tǒng)的頂層設(shè)計(jì)。但是,這些方框和箭頭叫什么?我們經(jīng)常使用諸如微服務(wù),實(shí)體,REST 或事件驅(qū)動(dòng)之類的術(shù)語(yǔ),這些又是什么?
作為研究論文的一部分,我一直在閱讀有關(guān)軟件體系結(jié)構(gòu)概念和定義的知識(shí),并且在整篇文章中,我將解釋其中的一些概念,這些概念適用于我在研究生期間也一直在從事的項(xiàng)目:JSON- RPC Playground 控制臺(tái)。
軟件架構(gòu)是什么?
我將使用 Roy Fielding(HTTP 規(guī)范的主要作者之一和 REST 風(fēng)格的創(chuàng)建者)在其博士學(xué)位論文中給出的定義(如果您對(duì) Software Architecture 感興趣,我推薦您看看這篇論文)。
軟件體系結(jié)構(gòu)是軟件系統(tǒng)在其操作的某個(gè)階段的運(yùn)行時(shí)元素的抽象。一個(gè)系統(tǒng)可能由許多抽象級(jí)別和許多操作組成,每個(gè)階段都有自己的軟件體系結(jié)構(gòu)。
架構(gòu)是抽象的
在描述體系結(jié)構(gòu)時(shí),可以對(duì)實(shí)現(xiàn)細(xì)節(jié)進(jìn)行抽象,以簡(jiǎn)化設(shè)計(jì)。如用 Elixir 或 Java 編寫身份驗(yàn)證服務(wù)有關(guān)系嗎,顯然這不是在架構(gòu)層面要考慮的事情?還是它在系統(tǒng)中扮演的角色 - 驗(yàn)證用戶 - 我們應(yīng)該關(guān)注什么?
架構(gòu)與運(yùn)行時(shí)有關(guān)
源代碼結(jié)構(gòu)不是系統(tǒng)的體系結(jié)構(gòu)。當(dāng)系統(tǒng)處于活動(dòng)狀態(tài)時(shí),不同的應(yīng)用程序可以共享公共庫(kù)或模塊,但彼此之間完全斷開連接,這個(gè)是低耦合的設(shè)計(jì)。我們專注于處理數(shù)據(jù)以及如何移動(dòng)數(shù)據(jù)。軟件架構(gòu)一般用高內(nèi)聚、低耦合方式構(gòu)建。
架構(gòu)專注于特定的階段
并非每個(gè)組件都會(huì)始終發(fā)揮作用或成為各個(gè)流程的一部分。系統(tǒng)關(guān)閉時(shí)涉及的組件及其配置可能與系統(tǒng)正常操作模式中涉及的組件及其配置完全不同。
架構(gòu)是嵌套的
在抽象元素實(shí)現(xiàn)時(shí),我們將忽略與當(dāng)前架構(gòu)無(wú)關(guān)的細(xì)節(jié)。如果我們將重點(diǎn)轉(zhuǎn)移到更深層次,則將出現(xiàn)具有其自身的元素和配置集的新體系結(jié)構(gòu)。我們將找到許多嵌套的體系結(jié)構(gòu),直到元素足夠簡(jiǎn)單以至于無(wú)法分解為止,實(shí)現(xiàn)原子性構(gòu)建。
一系列決策的結(jié)果是創(chuàng)建了一個(gè)軟件體系結(jié)構(gòu),每個(gè)決策都帶來(lái)了一組屬性和約束。無(wú)論它們?cè)趫D表中是明確指出的,還是僅存在于架構(gòu)師和開發(fā)人員的思想中,對(duì)于這些決定和約束應(yīng)該都有一種理解或意圖。隨著系統(tǒng)的發(fā)展,這些最初的決定很可能與架構(gòu)的實(shí)際情況不符。如果這些差異是不希望的或偶然的,那么我們說(shuō)該體系結(jié)構(gòu)已被侵蝕,造成了體系結(jié)構(gòu)的“技術(shù)負(fù)債”。
案例研究:JSON-RPC 控制臺(tái)
我們的客戶平臺(tái)之一是由數(shù)十個(gè) JVM 微服務(wù)組成,它們使用 JSON-RPC 2.0 協(xié)議相互通信。每個(gè)服務(wù)使用一組 Java 接口聲明其 RPC API,這些 Java 接口作為“ Service-API”庫(kù)(JAR)發(fā)布在公共存儲(chǔ)庫(kù)中。想要與服務(wù)進(jìn)行交互的客戶端只需將其 API JAR 聲明為項(xiàng)目依賴項(xiàng)即可。平臺(tái)庫(kù)將生成實(shí)現(xiàn)此類接口的對(duì)象,并通過(guò)依賴注入提供實(shí)現(xiàn)代碼類。從代碼角度來(lái)看,您只是在調(diào)用常規(guī)方法,但是在后臺(tái),平臺(tái)庫(kù)正在執(zhí)行 RPC 調(diào)用并為您處理所有涉及的管道。這在編寫代碼時(shí)極大地提高了工作效率!
但是,要手動(dòng)測(cè)試所有這些 RPC 方法(例如,使用諸如 Postman 或 curl 的工具),就必須找到正確的代碼庫(kù),手動(dòng)檢查服務(wù)接口,其方法和參數(shù)(可能具有許多嵌套對(duì)象級(jí)別) ),然后手動(dòng)構(gòu)建所需的 JSON payout 以執(zhí)行 API 調(diào)用。一般而言,API 文檔有幫助,但是很難保持最新,這是一個(gè)問題。
這里,我創(chuàng)建一個(gè) GUI 應(yīng)用程序,該應(yīng)用程序會(huì)自動(dòng)生成可輕松填充的表單,以調(diào)用服務(wù)公開的任何 RPC 方法。這些表單是通過(guò)與 JSON-RPC 2.0 兼容的服務(wù)描述文件生成的,該文件是通過(guò)分析 Service-API JAR 庫(kù)創(chuàng)建的。通過(guò)使用與生產(chǎn)中運(yùn)行的實(shí)際代碼相同的源,可以確保它們保持最新。

架構(gòu)元素
構(gòu)架系統(tǒng)意味著要做出一系列決定,這些決定可以塑造構(gòu)成系統(tǒng)的不同元素(組件,連接器和數(shù)據(jù))的配置。
組件(Components)
組件是軟件指令和內(nèi)部狀態(tài)的抽象單元,它通過(guò)接口提供轉(zhuǎn)換或執(zhí)行數(shù)據(jù)計(jì)算。組件是由它們向其他組件提供的服務(wù)定義的,而不是由它們的界面后面的實(shí)現(xiàn)定義的。如果其他組件無(wú)法識(shí)別某些行為,則該行為不是體系結(jié)構(gòu)的一部分。
示例
- RPC 控制臺(tái):將服務(wù)描述轉(zhuǎn)換為一組表單,捕獲用戶輸入,執(zhí)行 RPC 調(diào)用并顯示其結(jié)果。
- RPC 服務(wù)器:接收 RPC 請(qǐng)求,對(duì)其進(jìn)行計(jì)算,然后返回結(jié)果。
- 分析器:將 Service-API JAR 轉(zhuǎn)換為服務(wù)描述。
- JAR 存儲(chǔ)庫(kù):存儲(chǔ)并提供 Service-API JAR。
- 服務(wù)說(shuō)明存儲(chǔ)庫(kù):存儲(chǔ)并提供服務(wù)說(shuō)明。
請(qǐng)注意,就此體系結(jié)構(gòu)的觀點(diǎn)而言,在定義 RPC Server 組件時(shí),我們對(duì) RPC Server 提供的特定功能不感興趣,因?yàn)樗c其余組件無(wú)關(guān)。我們甚至將這個(gè)組件的許多不同實(shí)例均等地分組,即使實(shí)際上它們?cè)诠δ苌蠒?huì)有很大不同。如,一個(gè)可能是 Users 服務(wù),而另一個(gè)可能是 Books 服務(wù)。
連接器(Connectors)
連接器可實(shí)現(xiàn)不同組件之間的通信和數(shù)據(jù)傳輸。他們不轉(zhuǎn)換數(shù)據(jù),而是通過(guò)界面在不同組件之間對(duì)數(shù)據(jù)進(jìn)行移動(dòng)。但是在內(nèi)部,當(dāng)查看一個(gè)特定連接器的體系結(jié)構(gòu)時(shí),我們可能會(huì)發(fā)現(xiàn)它實(shí)際上是由一個(gè)子系統(tǒng)組成的,這些子系統(tǒng)接收數(shù)據(jù),將其轉(zhuǎn)換為更好的格式以進(jìn)行傳輸,將其發(fā)送到另一端,然后反轉(zhuǎn)轉(zhuǎn)換,然后再傳遞給系統(tǒng)的其余部分。由于這些轉(zhuǎn)換對(duì)系統(tǒng)的其余部分不可見,因此我們可以將它們抽象化為更高的層次。
在示例中:
- RPC 客戶端:開始 RPC 調(diào)用。
- RPC 服務(wù)器:接收 RPC 請(qǐng)求并返回 RPC 響應(yīng)。
- HTTP 客戶端:?jiǎn)?dòng) HTTP 連接以獲取服務(wù)描述。
- AWS 庫(kù):將服務(wù)描述從分析器傳輸?shù)椒?wù)描述存儲(chǔ)庫(kù)。
- Gradle 庫(kù):將 Service-API JAR 依賴項(xiàng)從 JAR 存儲(chǔ)庫(kù)傳輸?shù)椒治銎鳌?/li>
對(duì)于 AWS Library 和 Gradle Library 而言,我們不直接負(fù)責(zé)這些數(shù)據(jù)傳輸?shù)姆绞?。然后,我們可以使用連接器的視圖,而忽略其實(shí)現(xiàn)的細(xì)節(jié)。
數(shù)據(jù) (data)
許多軟件體系結(jié)構(gòu)定義沒有將數(shù)據(jù)作為核心概念提及,我認(rèn)為這并不完整。數(shù)據(jù)是系統(tǒng)存在的原因,有時(shí)甚至是驅(qū)動(dòng)系統(tǒng)配置的主要因素。數(shù)據(jù)定義為通過(guò)連接器從一個(gè)組件傳輸?shù)搅硪唤M件的信息。
在示例中:
- 服務(wù)描述:以 JSON-RPC 2.0 兼容結(jié)構(gòu)描述服務(wù)公開的可用 RPC 方法。它包括服務(wù)器 URL,方法名稱,參數(shù)和類型之類的信息。
- RPC 請(qǐng)求:包括 RPC 方法名稱及其參數(shù)。
- RPC 結(jié)果:RPC 調(diào)用執(zhí)行的結(jié)果。
- Service-API JAR:包含 RPC 服務(wù)的 Java 接口的 JAR 文件。
架構(gòu)風(fēng)格
架構(gòu)風(fēng)格是架構(gòu)設(shè)計(jì)決策的命名集合,當(dāng)在特定上下文中應(yīng)用時(shí)對(duì)應(yīng)不同的系統(tǒng)元素,它們的配置以及它們之間的關(guān)聯(lián)方式施加約束,進(jìn)而生成具有眾所周知架構(gòu)解決方案。
樣式是一種用于對(duì)體系結(jié)構(gòu)進(jìn)行分類并定義其共同特征的機(jī)制。每種樣式都為組件的交互提供了抽象,通過(guò)忽略架構(gòu)其余部分的細(xì)節(jié)來(lái)捕獲交互模式的本質(zhì)。樣式可以僅關(guān)注體系結(jié)構(gòu)的某些方面,甚至可以將它們組合以生成更復(fù)雜的樣式或混合樣式。
客戶端 - 服務(wù)器,微服務(wù),Monolithic 甚至是 REST 都是不同的體系結(jié)構(gòu)樣式,您很可能已將其應(yīng)用于數(shù)十種異構(gòu)系統(tǒng)。
創(chuàng)造自己的風(fēng)格
如果您熟悉諸如 Swagger 的 REST API 之類的工具,您可能會(huì)注意到我的 JSON-RPC 項(xiàng)目與之相似。雖然我的控制臺(tái)使用了針對(duì)基于 JSON-RPC 的服務(wù)量身定制的服務(wù)描述作為輸入,但是 REST API 具有OpenAPI 標(biāo)準(zhǔn)。從服務(wù)的源代碼生成規(guī)范格式是一種強(qiáng)大的模式,可用于創(chuàng)建許多不同的使用者工具:文檔導(dǎo)航器,客戶端代碼生成器,模擬服務(wù)器等。
讓我們嘗試為該工具系列定義通用的體系結(jié)構(gòu)樣式,該樣式可以應(yīng)用于任何其他協(xié)議以獲得相同的好處:我將其稱為“服務(wù)描述”樣式。
服務(wù)風(fēng)格描述
讓我們開始定義架構(gòu)的不同元素
數(shù)據(jù)元素:
- 目標(biāo)源代碼:目標(biāo)服務(wù)接口的源代碼。
- 服務(wù)描述:特定于協(xié)議的格式,遵循協(xié)議標(biāo)準(zhǔn),可以描述任何目標(biāo)服務(wù)的接口。
組件:
- 生成器:自動(dòng)從目標(biāo)源代碼創(chuàng)建服務(wù)描述,并將其發(fā)布到提供者。
- 存儲(chǔ)庫(kù):存儲(chǔ)并提供服務(wù)說(shuō)明。
- 客戶端:使用存儲(chǔ)庫(kù)中的服務(wù)描述,并將其用作提供針對(duì)目標(biāo)服務(wù)動(dòng)態(tài)定制功能的唯一來(lái)源。
連接器 (connectors):
- 生成器 -> 存儲(chǔ)庫(kù):將服務(wù)描述從生成器傳輸?shù)酱鎯?chǔ)庫(kù)。
- 存儲(chǔ)庫(kù) -> 客戶端:將服務(wù)描述從存儲(chǔ)庫(kù)傳輸?shù)娇蛻舳恕?/li>
必須從源代碼創(chuàng)建服務(wù)描述。客戶端需要始終保持最新的服務(wù)說(shuō)明才能正常運(yùn)行,因?yàn)槌欠?wù)說(shuō)明中包含客戶信息,否則他們對(duì)目標(biāo)服務(wù)的具體情況一無(wú)所知。主要來(lái)源是代碼,如果流程不是自動(dòng)化的,則很可能會(huì)出現(xiàn)服務(wù)描述過(guò)時(shí)且客戶端損壞的風(fēng)險(xiǎn)。這并不意味著不能手動(dòng)構(gòu)建服務(wù)描述。這樣做有很多有效的用例,例如,如果您想在實(shí)際實(shí)現(xiàn)之前擁有一個(gè)模擬服務(wù)器。但是,依賴于手動(dòng)任務(wù)的系統(tǒng)將不被視為該體系結(jié)構(gòu)樣式的實(shí)現(xiàn)。
請(qǐng)注意,我們對(duì)生成器如何使用源代碼沒有任何限制。實(shí)際上,生成器甚至可以作為目標(biāo)構(gòu)建過(guò)程中的一個(gè)步驟來(lái)實(shí)現(xiàn)(例如,使用 Maven 插件)。服務(wù)描述應(yīng)遵循協(xié)議標(biāo)準(zhǔn)。該體系結(jié)構(gòu)的主要優(yōu)點(diǎn)之一是客戶端可針對(duì)使用同一協(xié)議的許多不同目標(biāo)服務(wù)進(jìn)行重用,因此,服務(wù)描述無(wú)法了解僅適用于一項(xiàng)特定服務(wù)的特定實(shí)現(xiàn)細(xì)節(jié)??蛻舳颂峁┑墓δ懿粚儆隗w系結(jié)構(gòu)的一部分:客戶端可以與目標(biāo)服務(wù)(例如,用于 Playground 控制臺(tái))進(jìn)行交互,或者根本不進(jìn)行交互(對(duì)于靜態(tài)文檔而言)??蛻舯澈蟮闹饕拗剖?,除了服務(wù)描述所包含的信息外,他們還應(yīng)該對(duì)目標(biāo)服務(wù)的實(shí)施細(xì)節(jié)一無(wú)所知。連接器的定義非常寬松,因?yàn)槲覀儗?duì)信息的傳輸方式?jīng)]有任何限制。
- 原文標(biāo)題:工程師每天都在研究的軟件架構(gòu)是什么?
- 原文地址:https://www.infoq.cn/article/9FCBWR5yHFlvzpg1DrUO