Categories
程式開發

工程師每天都在研究的軟件架構是什麼?


在某些時候,工程師必須繪製一些方框和箭頭來描述軟件系統的頂層設計。但是,這些方框和箭頭叫什麼?我們經常使用諸如微服務,實體,REST或事件驅動之類的術語,這些又是什麼?

作為研究論文的一部分,我一直在閱讀有關軟件體系結構概念和定義的知識,並且在整篇文章中,我將解釋其中的一些概念,這些概念適用於我在研究生期間也一直在從事的項目: JSON- RPC Playground控制台。

軟件架構是什麼?

我將使用Roy Fielding(HTTP規範的主要作者之一和REST風格的創建者)在其博士學位論文中給出的定義(如果您對Software Architecture感興趣,我推薦您看看這篇論文)。

軟件體系結構是軟件系統在其操作的某個階段的運行時元素的抽象。一個系統可能由許多抽象級別和許多操作組成,每個階段都有自己的軟件體系結構。

架構是抽象的

在描述體系結構時,可以對實現細節進行抽象,以簡化設計。如用Elixir或Java編寫身份驗證服務有關係嗎,顯然這不是在架構層面要考慮的事情?還是它在系統中扮演的角色-驗證用戶-我們應該關注什麼?

架構與運行時有關

源代碼結構不是系統的體系結構。當系統處於活動狀態時,不同的應用程序可以共享公共庫或模塊,但彼此之間完全斷開連接,這個是低耦合的設計。我們專注於處理數據以及如何移動數據。軟件架構一般用高內聚、低耦合方式構建。

架構專注於特定的階段

並非每個組件都會始終發揮作用或成為各個流程的一部分。系統關閉時涉及的組件及其配置可能與系統正常操作模式中涉及的組件及其配置完全不同。

架構是嵌套的

在抽像元素實現時,我們將忽略與當前架構無關的細節。如果我們將重點轉移到更深層次,則將出現具有其自身的元素和配置集的新體系結構。我們將找到許多嵌套的體系結構,直到元素足夠簡單以至於無法分解為止,實現原子性構建。

一系列決策的結果是創建了一個軟件體系結構,每個決策都帶來了一組屬性和約束。無論它們在圖表中是明確指出的,還是僅存在於架構師和開發人員的思想中,對於這些決定和約束應該都有一種理解或意圖。隨著系統的發展,這些最初的決定很可能與架構的實際情況不符。如果這些差異是不希望的或偶然的,那麼我們說該體系結構已被侵蝕,造成了體系結構的“技術負債”。

案例研究:JSON-RPC控制台

我們的客戶平台之一是由數十個JVM微服務組成,它們使用JSON-RPC 2.0協議相互通信。每個服務使用一組Java接口聲明其RPC API,這些Java接口作為“ Service-API”庫(JAR)發佈在公共存儲庫中。想要與服務進行交互的客戶端只需將其API JAR聲明為項目依賴項即可。平台庫將生成實現此類接口的對象,並通過依賴注入提供實現代碼類。從代碼角度來看,您只是在調用常規方法,但是在後台,平台庫正在執行RPC調用並為您處理所有涉及的管道。這在編寫代碼時極大地提高了工作效率!

但是,要手動測試所有這些RPC方法(例如,使用諸如Postman或curl的工具),就必須找到正確的代碼庫,手動檢查服務接口,其方法和參數(可能具有許多嵌套對象級別) ),然後手動構建所需的JSON payout以執行API調用。一般而言,API文檔有幫助,但是很難保持最新,這是一個問題。

這裡,我創建一個GUI應用程序,該應用程序會自動生成可輕鬆填充的表單,以調用服務公開的任何RPC方法。這些表單是通過與JSON-RPC 2.0兼容的服務描述文件生成的,該文件是通過分析Service-API JAR庫創建的。通過使用與生產中運行的實際代碼相同的源,可以確保它們保持最新。

工程師每天都在研究的軟件架構是什麼? 1

架構元素

構架系統意味著要做出一系列決定,這些決定可以塑造構成系統的不同元素(組件,連接器和數據)的配置。

組件(Components)

組件是軟件指令和內部狀態的抽象單元,它通過接口提供轉換或執行數據計算。組件是由它們向其他組件提供的服務定義的,而不是由它們的界面後面的實現定義的。如果其他組件無法識別某些行為,則該行為不是體系結構的一部分。

示例

  • RPC控制台:將服務描述轉換為一組表單,捕獲用戶輸入,執行RPC調用並顯示其結果。
  • RPC服務器:接收RPC請求,對其進行計算,然後返回結果。
  • 分析器:將Service-API JAR轉換為服務描述。
  • JAR存儲庫:存儲並提供Service-API JAR。
  • 服務說明存儲庫:存儲並提供服務說明。

請注意,就此體系結構的觀點而言,在定義RPC Server組件時,我們對RPC Server提供的特定功能不感興趣,因為它與其餘組件無關。我們甚至將這個組件的許多不同實例均等地分組,即使實際上它們在功能上會有很大不同。如,一個可能是Users服務,而另一個可能是Books服務。

連接器(Connectors)

連接器可實現不同組件之間的通信和數據傳輸。他們不轉換數據,而是通過界面在不同組件之間對數據進行移動。但是在內部,當查看一個特定連接器的體系結構時,我們可能會發現它實際上是由一個子系統組成的,這些子系統接收數據,將其轉換為更好的格式以進行傳輸,將其發送到另一端,然後反轉轉換,然後再傳遞給系統的其餘部分。由於這些轉換對系統的其餘部分不可見,因此我們可以將它們抽象化為更高的層次。

在示例中:

  • RPC客戶端:開始RPC調用。
  • RPC服務器:接收RPC請求並返回RPC響應。
  • HTTP客戶端:啟動HTTP連接以獲取服務描述。
  • AWS庫:將服務描述從分析器傳輸到服務描述存儲庫。
  • Gradle庫:將Service-API JAR依賴項從JAR存儲庫傳輸到分析器。

對於AWS Library和Gradle Library而言,我們不直接負責這些數據傳輸的方式。然後,我們可以使用連接器的視圖,而忽略其實現的細節。

數據 (data)

許多軟件體系結構定義沒有將數據作為核心概念提及,我認為這並不完整。數據是系統存在的原因,有時甚至是驅動系統配置的主要因素。數據定義為通過連接器從一個組件傳輸到另一組件的信息。

在示例中:

  • 服務描述:以JSON-RPC 2.0兼容結構描述服務公開的可用RPC方法。它包括服務器URL,方法名稱,參數和類型之類的信息。
  • RPC請求:包括RPC方法名稱及其參數。
  • RPC結果:RPC調用執行的結果。
  • Service-API JAR:包含RPC服務的Java接口的JAR文件。

架構風格

架構風格是架構設計決策的命名集合,當在特定上下文中應用時對應不同的系統元素,它們的配置以及它們之間的關聯方式施加約束,進而生成具有眾所周知架構解決方案。

樣式是一種用於對體系結構進行分類並定義其共同特徵的機制。每種樣式都為組件的交互提供了抽象,通過忽略架構其餘部分的細節來捕獲交互模式的本質。樣式可以僅關注體系結構的某些方面,甚至可以將它們組合以生成更複雜的樣式或混合樣式。

客戶端-服務器,微服務,Monolithic甚至是REST都是不同的體系結構樣式,您很可能已將其應用於數十種異構系統。

創造自己的風格

如果您熟悉諸如Swagger的REST API之類的工具,您可能會注意到我的JSON-RPC項目與之相似。雖然我的控制台使用了針對基於JSON-RPC的服務量身定制的服務描述作為輸入,但是REST API具有OpenAPI標準。從服務的源代碼生成規範格式是一種強大的模式,可用於創建許多不同的使用者工具:文檔導航器,客戶端代碼生成器,模擬服務器等。

讓我們嘗試為該工具系列定義通用的體系結構樣式,該樣式可以應用於任何其他協議以獲得相同的好處:我將其稱為“服務描述”樣式。

服務風格描述

讓我們開始定義架構的不同元素

數據元素:

  • 目標源代碼:目標服務接口的源代碼。
  • 服務描述:特定於協議的格式,遵循協議標準,可以描述任何目標服務的接口。

組件:

  • 生成器:自動從目標源代碼創建服務描述,並將其發佈到提供者。
  • 存儲庫:存儲並提供服務說明。
  • 客戶端:使用存儲庫中的服務描述,並將其用作提供針對目標服務動態定制功能的唯一來源。

連接器(connectors):

  • 生成器->存儲庫:將服務描述從生成器傳輸到存儲庫。
  • 存儲庫->客戶端:將服務描述從存儲庫傳輸到客戶端。

必須從源代碼創建服務描述。客戶端需要始終保持最新的服務說明才能正常運行,因為除非服務說明中包含客戶信息,否則他們對目標服務的具體情況一無所知。主要來源是代碼,如果流程不是自動化的,則很可能會出現服務描述過時且客戶端損壞的風險。這並不意味著不能手動構建服務描述。這樣做有很多有效的用例,例如,如果您想在實際實現之前擁有一個模擬服務器。但是,依賴於手動任務的系統將不被視為該體系結構樣式的實現。

請注意,我們對生成器如何使用源代碼沒有任何限制。實際上,生成器甚至可以作為目標構建過程中的一個步驟來實現(例如,使用Maven插件)。服務描述應遵循協議標準。該體系結構的主要優點之一是客戶端可針對使用同一協議的許多不同目標服務進行重用,因此,服務描述無法了解僅適用於一項特定服務的特定實現細節。客戶端提供的功能不屬於體系結構的一部分:客戶端可以與目標服務(例如,用於Playground控制台)進行交互,或者根本不進行交互(對於靜態文檔而言)。客戶背後的主要限制是,除了服務描述所包含的信息外,他們還應該對目標服務的實施細節一無所知。連接器的定義非常寬鬆,因為我們對信息的傳輸方式沒有任何限制。