本文將給大家介紹一種簡潔明瞭軟體架構可視化模型——C4模型,並手把手教大家如何使用代碼繪製出精美的C4架構圖。 ...
作者:京東物流 覃玉傑
1. 前言
本文將給大家介紹一種簡潔明瞭軟體架構可視化模型——C4模型,並手把手教大家如何使用代碼繪製出精美的C4架構圖。
閱讀本文之後,讀者畫的架構圖將會是這樣的:
註:該圖例僅作繪圖示例使用,不確保其完整性、可行性。
2. C4模型
2.1 C4模型整體介紹
C4是軟體架構可視化的一種方案。架構可視化,指的是用圖例的方式,把軟體架構設計準確、清晰、美觀地表示出來。架構可視化不是指導開發者如何進行架構設計,而是指導開發者將架構設計表達出來,產出簡潔直觀的架構圖。
架構可視化的方法有很多,主流的有“4+1”視圖模型、C4模型。視圖模型描述的是架構本身,架構確定之後,不管用什麼模型去表達,本質上都應該是一樣的,並沒有優劣之分。
C4 模型是一種易於學習、對開發人員友好的軟體架構圖示方法,C4模型沒有規定使用特定的圖形、特定的建模語言來畫圖,因而使用者可以非常靈活地產出架構圖。
C4模型將系統從上往下分為System Context, Containers, Components, Code四層視圖,每一層都是對上一層的完善和展開,層層遞進地對系統進行描述,如下圖。
2.2 System Context diagram
System Context(系統上下文)視圖位於頂層,是軟體系統架構圖的起點,表達的是系統的全貌。System Context視圖重點展示的是系統邊界、系統相關的用戶、其他支撐系統以及與本系統的交互。本層不涉及到具體細節(例如技術選型、協議、部署方案和其他低級細節),因此System Context可以很好地向非技術人員介紹系統。
作用:清晰地展示待構建的系統、用戶以及現有的IT基礎設施。
範圍:待描述的核心系統以及其相關用戶、支撐系統,不應該出現與核心系統無關的其他系統。例如我們要描述一個打車系統,不應該把無關聯的藥店系統繪製進去,並且要確保一個System Context只有一個待描述的軟體系統。
主要元素:Context內待描述的軟體系統。
支持元素:在範圍內直接與主要元素中的軟體系統有關聯的人員(例如用戶、參與者、角色或角色)和外部依賴系統。通常,這些外部依賴系統位於我們自己的軟體系統邊界之外。
目標受眾:軟體開發團隊內外的所有人,包括技術人員和非技術人員。
推薦給大多數團隊:是的。
示例:
這是該網上銀行系統的系統上下文圖。它顯示了使用它的人,以及與該系統有關係的其他軟體系統。網上銀行系統是將要建設的系統,銀行的個人客戶使用網上銀行系統查看其銀行賬戶的信息併進行支付。網上銀行系統本身使用銀行現有的大型機銀行系統來執行此操作,並使用銀行現有的電子郵件系統向客戶發送電子郵件。
圖例:
2.3 Container diagram
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 應用程式和資料庫。
- Web 應用程式是一個 Java/Spring MVC Web 應用程式,它只提供構成單頁應用程式的靜態內容(HTML、CSS 和 JS)。
- 單頁應用程式是在客戶的網路瀏覽器中運行的 Angular 應用程式,是網上銀行功能的前端。
- 客戶也可以使用跨平臺 Xamarin 移動應用程式來訪問網上銀行。
- 單頁應用程式和移動應用程式都使用 JSON+HTTPS API,該 API 由運行在伺服器上的另一個 Java/Spring MVC 應用程式提供。
- API 應用程式從關係資料庫中獲取用戶信息。
- API 應用程式還使用專有的 XML/HTTPS 介面與現有的大型機銀行系統進行通信,以獲取有關銀行賬戶的信息或進行交易。
- 如果 API 應用程式需要向客戶發送電子郵件,它也會使用現有的電子郵件系統。
該容器圖的圖例如下,主要是引入了資料庫、APP、瀏覽器的圖例。
2.4 Component diagram
將單個容器放大,則顯示了該容器內部的組件。Component(組件)視圖顯示了一個容器是如何由許多“組件”組成的,每個組件是什麼,它們的職責以及技術實現細節。
作用:展示了可執行的容器內部構成與分工,可直接指導開發。
範圍:單個容器。
主要元素:範圍內容器內的組件,通常可以是Dubbo介面、REST介面、Service、Dao等。
支持元素:直接連接到容器的人員和外部依賴系統。
目標受眾:軟體架構師和開發人員。
推薦給大多數團隊:Component用於指導開發,當有需要時創建。
示例:
圖例:
2.5 Code diagram
放大組件視圖,則得到出組件的Code視圖(代碼視圖)。
Code視圖一般採用 UML 類圖、ER圖等。Code視圖是一個可選的詳細級別,通常可以通過 IDE 等工具按需生成。除了最重要或最複雜的組件外,不建議將這種詳細程度用於其他任何內容。
在註重敏捷開發的今天,一般不建議產出Code視圖。
範圍:單個組件。
主要元素:範圍內組件內的代碼元素(例如類、介面、對象、函數、資料庫表等)。
目標受眾:軟體架構師和開發人員。
推薦給大多數團隊:不,大多數 IDE 可以按需生成這種級別的詳細信息。
2.6 System Landscape diagram
C4 模型提供了單個軟體系統的靜態視圖,不管是 System Context、Container、Component都是針對單個軟體系統的進行描述的,但在實際中軟體系統不會孤立存在。為描述所有這些軟體系統如何在給定的企業、組織、部門等中與其他系統組合在一起,C4採用擴展視圖System Landscape (系統景觀圖)。
系統景觀圖實際上只是一個沒有特定關註的軟體系統的系統上下文圖(System Context diagram),系統景觀圖內的軟體系統都可以採用C4進行深入分析。
適用範圍:企業/組織/部門/等。
主要元素:與所選範圍相關的人員和軟體系統。
目標受眾:軟體開發團隊內外的技術人員和非技術人員。
示例:
圖例:
2.7 Dynamic diagram
Dynamic diagram(動態圖)用於展示靜態模型中的元素如何在運行時協作。動態圖允許圖表元素自由排列,並通過帶有編號的箭頭以指示執行順序。
範圍:特定功能、故事、用例等。
主要元素和支持元素:按照實際需要,可以是軟體系統、容器或組件。
目標受眾:軟體開發團隊內外的技術人員和非技術人員。
示例:
圖例:
2.8 Deployment diagram
Deployment diagram(部署圖)用於說明靜態模型中的軟體系統(或容器)的實例在給定環境(例如生產、測試、預發、開發等)中的部署方案。
C4的部署圖基於UML 部署圖,但為了突出顯示容器和部署節點之間的映射會做略微的簡化。
部署節點表示表示軟體系統/容器實例運行的位置,類似於物理基礎架構(例如物理伺服器或設備)、虛擬化基礎架構(例如 IaaS、PaaS、虛擬機)、容器化基礎架構(例如 Docker 容器)、執行環境(例如資料庫伺服器、Java EE web/應用伺服器、Microsoft IIS)等。部署節點可以嵌套,也可以將基礎設施節點包括進去,例如 DNS 服務、負載平衡器、防火牆等。
可以在部署圖中隨意使用 Amazon Web Services、Azure 等提供的圖標,只需確保被使用的任何圖標都包含在圖例中,不產生歧義。
範圍:單個部署環境中的一個或多個軟體系統(例如生產、暫存、開發等)。
主要元素:部署節點、軟體系統實例和容器實例。
支持元素:用於部署軟體系統的基礎設施節點。
目標受眾:軟體開發團隊內外的技術人員;包括軟體架構師、開發人員、基礎架構架構師和運營/支持人員。
示例:
網上銀行系統的開發環境部署圖:
圖例
網上銀行的生產環境部署圖:
圖例
2.9 C4模型規範以及Review CheckList
為了確保C4模型的架構圖的可讀性,C4模型提供了作圖規範,並且提供了CheckList供自查。
2.9.1 C4模型規範
- 圖表
每個圖都應該有一個描述圖類型和範圍的標題(例如“我的軟體系統的系統環境圖”)。
每個圖表都應該有一個關鍵/圖例來解釋所使用的符號(例如形狀、顏色、邊框樣式、線型、箭頭等)。
首字母縮略詞和縮寫詞(業務/領域或技術)應為所有受眾所理解,或在圖表鍵/圖例中進行解釋。
- 元素
應明確指定每個元素的類型(例如,人員、軟體系統、容器或組件)。
每個元素都應該有一個簡短的描述,以提供關鍵職責的“一目瞭然”的視圖。
每個容器和組件都應該有明確指定的技術。
- 關係
每條線都應該代表一個單向關係。
每一行都應該被標記,標記與關係的方向和意圖一致(例如依賴或數據流)。嘗試儘可能具體地使用標簽,最好避免使用“使用”等單個詞。
容器之間的關係(通常代表進程間通信)應該有明確標記的技術/協議。
2.9.2 Review Checklist
C4模型圖表繪製完成後,可以通過Review Checklist 進行自查,檢查是否有不規範之處。Review Checklist被製成網頁,可以通過 https://c4model.com/review/ 進行訪問。
3. C4模型架構圖代碼繪製實戰
3.1 文本繪圖工具選型
關於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
3.2 VS Code 下C4-PlantUML安裝
3.2.1 安裝VS Code
直接官網下載安裝即可,過程略去。
3.2.2 安裝PlantUML插件
在VS Code的Extensions視窗中搜索PlantUML,安裝PlantUML插件。
3.2.3 配置VS Code代碼片段
安裝完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
如圖:
3.2.4 安裝Graphviz
如果圖形渲染出現問題,提示安裝graphviz庫,直接到graphviz官網安裝即可。官網鏈接如下:
Mac系統推薦採用MacPorts安裝。
3.3 IntelliJ Idea 下C4-PlantUML安裝
3.3.1 安裝Idea
3.3.2 安裝PlantUML Integration插件
3.3.3 安裝代碼模版
通過以下鏈接,下載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即可。
3.4 案例實戰及C4-PlantUML語法介紹
C4-PlantUML的詳細語法可以到官網github項目主頁( https://github.com/plantuml-stdlib/C4-PlantUML )去瞭解,在此只做簡單介紹。
3.4.1 案例
以某招聘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
3.4.2 PlantUML文件
PlantUML文件以puml作為文件擴展名。
3.4.3 @startuml和@enduml
整個文檔由@startuml
和@enduml
包裹,是固定語法。
@startuml
@enduml
3.4.4 註釋
PlantUML中使用單引號(即'
)作為註釋標識。
3.4.5 include語句
首先是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")
3.4.6 C4模型靜態元素
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"){
}
3.4.7 C4模型的關係元素
Rel代表兩個元素之間的關係,其語法為:
Rel(from_alias, to_alias, "label", "technology")
from_alias是起點元素的別名,to_alias是終點元素的別名,label則用來說明這個關聯關係,technology代表採用的技術、通信協議。例如:
Rel(C_IOS, C_GATEWAY, "請求服務端","HTTPS")
代表iOS客戶端通過請求網關介面訪問服務端資源,採用HTTPS的通信方式。
建議在繪製Rel
時標註出technology
。
3.4.8 C4-PlantUML佈局
C4-PlantUML提供了多種自動佈局方案,我們可以根據實際需要進行選擇。
- LAYOUT_TOP_DOWN():從上往下佈局,預設採用該佈局。如下圖:
- LAYOUT_LEFT_RIGHT():從左到右,即橫向放置元素。
left to right direction
是PlantUML的語法,也可以直接用。
3.4.9 圖例
通過SHOW_LEGEND()
添加圖例。