作者:京東物流 覃玉傑
本文將給大家介紹一種簡潔明瞭軟體架構視覺化模型——C4模型,並手把手教大家如何使用程式碼繪製出精美的C4架構圖。
閱讀本文之後,讀者畫的架構圖將會是這樣的:
注:該圖例僅作繪圖範例使用,不確保其完整性、可行性。
C4是軟體架構視覺化的一種方案。架構視覺化,指的是用圖例的方式,把軟體架構設計準確、清晰、美觀地表示出來。架構視覺化不是指導開發者如何進行架構設計,而是指導開發者將架構設計表達出來,產出簡潔直觀的架構圖。
架構視覺化的方法有很多,主流的有「4+1」檢視模型、C4模型。檢視模型描述的是架構本身,架構確定之後,不管用什麼模型去表達,本質上都應該是一樣的,並沒有優劣之分。
C4 模型是一種易於學習、對開發人員友好的軟體架構圖示方法,C4模型沒有規定使用特定的圖形、特定的建模語言來畫圖,因而使用者可以非常靈活地產出架構圖。
C4模型將系統從上往下分為System Context, Containers, Components, Code四層檢視,每一層都是對上一層的完善和展開,層層遞進地對系統進行描述,如下圖。
System Context(系統上下文)檢視位於頂層,是軟體系統架構圖的起點,表達的是系統的全貌。System Context檢視重點展示的是系統邊界、系統相關的使用者、其他支撐系統以及與本系統的互動。本層不涉及到具體細節(例如技術選型、協定、部署方案和其他低階細節),因此System Context可以很好地向非技術人員介紹系統。
作用:清晰地展示待構建的系統、使用者以及現有的IT基礎設施。
範圍:待描述的核心繫統以及其相關使用者、支撐系統,不應該出現與核心系統無關的其他系統。例如我們要描述一個打車系統,不應該把無關聯的藥店系統繪製進去,並且要確保一個System Context只有一個待描述的軟體系統。
主要元素:Context內待描述的軟體系統。
支援元素:在範圍內直接與主要元素中的軟體系統有關聯的人員(例如使用者、參與者、角色或角色)和外部依賴系統。通常,這些外部依賴系統位於我們自己的軟體系統邊界之外。
目標受眾:軟體開發團隊內外的所有人,包括技術人員和非技術人員。
推薦給大多數團隊:是的。
範例:
這是該網上銀行系統的系統上下文圖。它顯示了使用它的人,以及與該系統有關係的其他軟體系統。網上銀行系統是將要建設的系統,銀行的個人客戶使用網上銀行系統檢視其銀行賬戶的資訊並進行支付。網上銀行系統本身使用銀行現有的大型電腦銀行系統來執行此操作,並使用銀行現有的電子郵件系統向客戶傳送電子郵件。
圖例:
Container(容器)檢視是對System Context的放大,是對System Context細節的補充。
注意這裡的容器,指的不是Docker等容器中介軟體。Container的描述範圍是一個可單獨執行/可部署的單元。Container一般指的是應用以及依賴的中介軟體,例如伺服器端 Web 應用程式、單頁應用程式、桌面應用程式、移動應用程式、資料庫架構、檔案系統、Redis、ElasticSeach、MQ等。
Container顯示了軟體架構的高階形狀以及系統內各容器之間的職責分工。
在Container這一層,還顯示了系統的主要的技術選型以及容器間的通訊和互動。
作用:展示系統整體的開發邊界,體現高層次的技術選型,暴露系統內容器之間的分工互動。
範圍:單個軟體系統,關注的系統內部的應用構成。
主要元素:軟體系統範圍內的容器,例如Spring Boot打包後的應用,MySQL資料庫、Redis、MQ等。
支援元素:直接使用容器的人員和外部依賴系統。
目標受眾:軟體開發團隊內外的技術人員,包括軟體架構師、開發人員和運營/支援人員。
推薦給大多數團隊:是的。
注意:Container檢視沒有說明部署方案、叢集、複製、故障轉移等。部署相關的檢視,會通過Deployment檢視進行展示。
範例:
網上銀行系統(此時System Contenxt中的系統已經被展開,所以用虛線框表示)由五個容器組成:伺服器端 Web 應用程式、單頁應用程式、移動應用程式、伺服器端 API 應用程式和資料庫。
該容器圖的圖例如下,主要是引入了資料庫、APP、瀏覽器的圖例。
將單個容器放大,則顯示了該容器內部的元件。Component(元件)檢視顯示了一個容器是如何由許多「元件」組成的,每個元件是什麼,它們的職責以及技術實現細節。
作用:展示了可執行的容器內部構成與分工,可直接指導開發。
範圍:單個容器。
主要元素:範圍內容器內的元件,通常可以是Dubbo介面、REST介面、Service、Dao等。
支援元素:直接連線到容器的人員和外部依賴系統。
目標受眾:軟體架構師和開發人員。
推薦給大多數團隊:Component用於指導開發,當有需要時建立。
範例:
圖例:
放大元件檢視,則得到出元件的Code檢視(程式碼檢視)。
Code檢視一般採用 UML 類圖、ER圖等。Code檢視是一個可選的詳細級別,通常可以通過 IDE 等工具按需生成。除了最重要或最複雜的元件外,不建議將這種詳細程度用於其他任何內容。
在注重敏捷開發的今天,一般不建議產出Code檢視。
範圍:單個元件。
主要元素:範圍內元件內的程式碼元素(例如類、介面、物件、函數、資料庫表等)。
目標受眾:軟體架構師和開發人員。
推薦給大多數團隊:不,大多數 IDE 可以按需生成這種級別的詳細資訊。
C4 模型提供了單個軟體系統的靜態檢視,不管是 System Context、Container、Component都是針對單個軟體系統的進行描述的,但在實際中軟體系統不會孤立存在。為描述所有這些軟體系統如何在給定的企業、組織、部門等中與其他系統組合在一起,C4採用擴充套件檢視System Landscape (系統景觀圖)。
系統景觀圖實際上只是一個沒有特定關注的軟體系統的系統上下文圖(System Context diagram),系統景觀圖內的軟體系統都可以採用C4進行深入分析。
適用範圍:企業/組織/部門/等。
主要元素:與所選範圍相關的人員和軟體系統。
目標受眾:軟體開發團隊內外的技術人員和非技術人員。
範例:
圖例:
Dynamic diagram(動態圖)用於展示靜態模型中的元素如何在執行時共同作業。動態圖允許圖表元素自由排列,並通過帶有編號的箭頭以指示執行順序。
範圍:特定功能、故事、用例等。
主要元素和支援元素:按照實際需要,可以是軟體系統、容器或元件。
目標受眾:軟體開發團隊內外的技術人員和非技術人員。
範例:
圖例:
Deployment diagram(部署圖)用於說明靜態模型中的軟體系統(或容器)的範例在給定環境(例如生產、測試、預發、開發等)中的部署方案。
C4的部署圖基於UML 部署圖,但為了突出顯示容器和部署節點之間的對映會做略微的簡化。
部署節點表示表示軟體系統/容器範例執行的位置,類似於物理基礎架構(例如物理伺服器或裝置)、虛擬化基礎架構(例如 IaaS、PaaS、虛擬機器器)、容器化基礎架構(例如 Docker 容器)、執行環境(例如資料庫伺服器、Java EE web/應用伺服器、Microsoft IIS)等。部署節點可以巢狀,也可以將基礎設施節點包括進去,例如 DNS 服務、負載平衡器、防火牆等。
可以在部署圖中隨意使用 Amazon Web Services、Azure 等提供的圖示,只需確保被使用的任何圖示都包含在圖例中,不產生歧義。
範圍:單個部署環境中的一個或多個軟體系統(例如生產、暫存、開發等)。
主要元素:部署節點、軟體系統範例和容器範例。
支援元素:用於部署軟體系統的基礎設施節點。
目標受眾:軟體開發團隊內外的技術人員;包括軟體架構師、開發人員、基礎架構架構師和運營/支援人員。
範例:
網上銀行系統的開發環境部署圖:
圖例
網上銀行的生產環境部署圖:
圖例
為了確保C4模型的架構圖的可讀性,C4模型提供了作圖規範,並且提供了CheckList供自查。
每個圖都應該有一個描述圖型別和範圍的標題(例如「我的軟體系統的系統環境圖」)。
每個圖表都應該有一個關鍵/圖例來解釋所使用的符號(例如形狀、顏色、邊框樣式、線型、箭頭等)。
首字母縮略詞和縮寫詞(業務/領域或技術)應為所有受眾所理解,或在圖表鍵/圖例中進行解釋。
應明確指定每個元素的型別(例如,人員、軟體系統、容器或元件)。
每個元素都應該有一個簡短的描述,以提供關鍵職責的「一目瞭然」的檢視。
每個容器和元件都應該有明確指定的技術。
每條線都應該代表一個單向關係。
每一行都應該被標記,標記與關係的方向和意圖一致(例如依賴或資料流)。嘗試儘可能具體地使用標籤,最好避免使用「使用」等單個詞。
容器之間的關係(通常代表程序間通訊)應該有明確標記的技術/協定。
C4模型圖表繪製完成後,可以通過Review Checklist 進行自查,檢查是否有不規範之處。Review Checklist被製成網頁,可以通過 https://c4model.com/review/ 進行存取。
關於C4模型的架構圖的繪製,一般有兩種方式:
第一種是採用繪圖工具,這類工具直接拖拽元素、調整樣式,即可產出圖片,例如draw.io、PPT等工具。繪圖工具的優點是非常靈活,可以滿足很多細節需求;缺點是通常調整元素的樣式會比較繁瑣。
第二種是採用基於文字的繪圖工具,根據一定的語法去描述圖片元素,最後根據文字自動渲染成圖片,例如PlantUML。基於文字的繪圖工具的優點是繪圖快捷,只要根據語法寫出描述檔案,即可渲染出來,元素的樣式已經預設偵錯好;缺點是樣式不一定符合我們的審美,調整不方便。
本文著重講解第二種,即基於文字的繪圖工具。
基於文字的繪圖工具有很多,例如:structurizr、PlantUML、mermaid,分別有自己的語法。
工具 | 語法 | 使用方式 | 地址 |
---|---|---|---|
structurizr | DSL | 提供Web介面渲染圖片,並且可以生成C4-PlantUML和mermaid的程式碼 | https://structurizr.com/ |
C4-PlantUML | PlantUML | VS Code外掛、IntelliJ Idea外掛 | https://github.com/plantuml-stdlib/C4-PlantUML |
mermaid | mermaid | Markdown外掛,提供Live Editor | https://mermaid.js.org/syntax/c4c.html ,Mermaid Live Editor |
由於IntelliJ Idea、VS Code目前在開發者中非常普及,我們選擇使用C4-PlantUML,結合VS Code和IntelliJ Idea分別進行C4模型的繪製。
VS Code環境的安裝,見3.2。
IntelliJ Idea環境的安裝,見3.3
直接官網下載安裝即可,過程略去。
在VS Code的Extensions視窗中搜尋PlantUML,安裝PlantUML外掛。
安裝完PlantUML之後,為了提高效率,我們最好安裝PlantUML相關的程式碼片段。
開啟VS Code選單,層級為Code→Preferences→User Snippets,如下圖:
在選擇Snippets File Or Create Snippets彈窗中,選擇New Global Snippets file,如下圖:
在接下來的彈窗中,輸入Snippets file的檔名,如下圖:
使用瀏覽器開啟以下連結,並將瀏覽器返回的文字內容貼上到VS Code編輯區
https://github.com/plantuml-stdlib/C4-PlantUML/blob/master/.vscode/C4.code-snippets
如圖:
如果圖形渲染出現問題,提示安裝graphviz庫,直接到graphviz官網安裝即可。官網連結如下:
Mac系統推薦採用MacPorts安裝。
通過以下連結,下載IntelliJ live template。
https://github.com/plantuml-stdlib/C4-PlantUML/blob/master/intellij/c4_live_template.zip
通過選單路徑 File | Manage IDE Settings | Import Settings
,選擇下載的 ZIP檔案, c4_live_template.zip
,匯入並重啟Idea即可。
C4-PlantUML的詳細語法可以到官網github專案主頁( https://github.com/plantuml-stdlib/C4-PlantUML )去了解,在此只做簡單介紹。
以某招聘APP伺服器端架構圖(Container級)為例子進行講解,以下是渲染出來的效果圖。
以下是完整plantuml程式碼:
@startuml
!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Context.puml
!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml
!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Component.puml
!define SPRITESURL https://raw.githubusercontent.com/rabelenda/cicon-plantuml-sprites/master/sprites
!define DEVICONS https://raw.githubusercontent.com/tupadr3/plantuml-icon-font-sprites/master/devicons
!define DEVICONS2 https://raw.githubusercontent.com/tupadr3/plantuml-icon-font-sprites/master/devicons2
!define FONTAWESOME https://raw.githubusercontent.com/tupadr3/plantuml-icon-font-sprites/master/font-awesome-5
!include DEVICONS/java.puml
!include DEVICONS/mysql.puml
!include DEVICONS2/spring.puml
!include DEVICONS2/redis.puml
!include DEVICONS2/android.puml
!include DEVICONS2/apple_original.puml
title 招聘APP架構圖(Container)
Person(P_User, "找工作的APP使用者(應聘者)")
System_Boundary(Boundary_APP, "招聘APP系統邊界"){
Container(C_ANDROID, "安卓行動端", "android", "移動APP安卓端",$sprite="android")
Container(C_IOS, "iOS行動端", "iOS", "移動APP iOS端",$sprite="apple_original")
Container(C_GATEWAY, "HTTP閘道器", "Netty", "鑑權、協定轉換",$sprite="java")
Container(C_GATEWAY_CACHE, "閘道器快取", "Redis", "快取認證憑據",$sprite="redis")
Container(C_BFF, "BFF閘道器", "Spring Boot","整合後端介面",$sprite="spring")
Container(C_CERT, "實名認證服務", "Spring Boot", "內部實名認證服務",$sprite="spring")
Container(C_BIZ_1, "職位服務", "Spring Boot", "釋出、搜尋職位",$sprite="spring")
Container(C_PAYMENT, "支付服務", "Spring Boot", "內部支付服務",$sprite="spring")
ContainerDb(CDB_MYSQL, "職位資訊資料庫", "MySQL", "持久化職位資訊",$sprite="mysql")
}
System_Ext(OUT_S_CERT, "實名認證服務","對使用者進行姓名身份證號實名認證")
System_Ext(OUT_S_PAYMENT, "第三方支付服務","支援使用者使用多種支付方式完成支付")
Rel(P_User, C_ANDROID, "註冊登陸投遞簡歷")
Rel(P_User, C_IOS, "註冊登陸投遞簡歷")
Rel(C_ANDROID, C_GATEWAY, "請求伺服器端","HTTPS")
Rel(C_IOS, C_GATEWAY, "請求伺服器端","HTTPS")
Rel_L(C_GATEWAY, C_GATEWAY_CACHE, "讀寫快取","jedis")
Rel(C_GATEWAY, C_BFF, "將HTTP協定轉為RPC協定","RPC")
Rel(C_GATEWAY, C_BIZ_1, "將HTTP協定轉為RPC協定","RPC")
Rel(C_GATEWAY, C_PAYMENT, "將HTTP協定轉為RPC協定","RPC")
Rel(C_BFF, C_CERT, "通過BFF處理之後,對外暴露介面服務","RPC")
Rel(C_BIZ_1, CDB_MYSQL, "讀寫資料","JDBC")
Rel(C_CERT, OUT_S_CERT, "對接外部查詢實名資訊介面","HTTPS")
Rel(C_PAYMENT, OUT_S_PAYMENT, "對接外部支付系統","HTTPS")
left to right direction
SHOW_LEGEND()
@enduml
PlantUML檔案以puml作為副檔名。
整個檔案由@startuml
和@enduml
包裹,是固定語法。
@startuml
@enduml
PlantUML中使用單引號(即'
)作為註釋標識。
首先是C4各個檢視的include語句,以下語句代表引入了C4的Context、Container、Component檢視。
!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Context.puml
!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml
!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Component.puml
其次是圖示庫:
!define SPRITESURL https://raw.githubusercontent.com/rabelenda/cicon-plantuml-sprites/master/sprites
!define DEVICONS https://raw.githubusercontent.com/tupadr3/plantuml-icon-font-sprites/master/devicons
!define DEVICONS2 https://raw.githubusercontent.com/tupadr3/plantuml-icon-font-sprites/master/devicons2
!define FONTAWESOME https://raw.githubusercontent.com/tupadr3/plantuml-icon-font-sprites/master/font-awesome-5
!include DEVICONS/java.puml
!include DEVICONS/mysql.puml
!include DEVICONS2/spring.puml
!include DEVICONS2/redis.puml
!include DEVICONS2/android.puml
!include DEVICONS2/apple_original.puml
注意這裡有一個define語法,先通過!define定義一個標識,之後使用該標識的地方都會被替換
!define DEVICONS2 https://raw.githubusercontent.com/tupadr3/plantuml-icon-font-sprites/master/devicons2
!include DEVICONS2/spring.puml
‘ 等價於 !include https://raw.githubusercontent.com/tupadr3/plantuml-icon-font-sprites/master/devicons2/spring.puml
使用圖示時,只需要在元素的宣告語句中加入$sprite="xxx"
即可。
ContainerDb(CDB_MYSQL, "職位資訊資料庫", "MySQL", "持久化職位資訊",$sprite="mysql")
Person:系統的使用者,可能是人或者其他系統
System:代表即將建設的系統,通常渲染為藍色方塊。
System_Ext:代表已存在的系統,通常渲染為灰色方塊。
System_Boundary:某系統展開為容器時,則將System改為System_Boundary,代表系統的邊界,內部放置容器元素,通常渲染為虛線框。
Container:待建設的容器,通常渲染為藍色方塊。
Container_Ext:已建設容器,通常渲染為灰色方塊。
Container_Boundary:某容器展開為元件之後,則將Container改為Container_Boundary,代表容器的邊界,內部放置元件元素,通常渲染為虛線框。
ContainerDb:待建設資料庫,通常渲染為藍色圓柱。
ContainerQueue:待建設訊息佇列,通常渲染為水平放置的藍色圓柱。
Component:待建設元件,通常渲染為藍色方塊。
Component_Ext:已建設元件,通常渲染為灰色方塊。
靜態元素的語法為:
Container(alias, "label", "technology", "description")
alias:是圖內元素的唯一ID,其他地方可以通過alias進行參照,比如在Rel
中參照
label:代表元素的顯示名稱
technology:代表元素採用的核心技術,包括但不限於開發語言、框架、通訊協定等
description:代表元素的簡單描述
對於System_Boundary和Container_Boundary,則只需要alias和label,大括號內是該元素邊界內的子元素。
Container_Boundary(alias, "label"){
}
Rel代表兩個元素之間的關係,其語法為:
Rel(from_alias, to_alias, "label", "technology")
from_alias是起點元素的別名,to_alias是終點元素的別名,label則用來說明這個關聯關係,technology代表採用的技術、通訊協定。例如:
Rel(C_IOS, C_GATEWAY, "請求伺服器端","HTTPS")
代表iOS使用者端通過請求閘道器介面存取伺服器端資源,採用HTTPS的通訊方式。
建議在繪製Rel
時標註出technology
。
C4-PlantUML提供了多種自動佈局方案,我們可以根據實際需要進行選擇。
left to right direction
是PlantUML的語法,也可以直接用。通過SHOW_LEGEND()
新增圖例。