使用C4模型创建软件架构图
前言
作为一名开发人员,你可能在某个时候会参与一个复杂的项目,尝试理解其代码库就像阅读一整本小说一样困难。即使是最优秀的工程师也会在庞大的代码中迷失方向。
问题在于,架构图(如果存在的话)通常是过时的遗物,来自一个早已逝去的时代。
这就是为什么创建和维护有效且清晰的图表应该是毫不费力的。及时更新的可视化图表确保每个人都保持在同一页面上,消除混淆和浪费的时间。
目录
什么是C4模型?
C4模型的创建是为了帮助软件开发团队描述和交流软件架构。
C4代表"上下文(Context)、容器(Containers)、组件(Components)和代码(Code)"。这四个层级足以描述一个复杂的系统。
解释这个概念的最佳方式是想想我们如何使用谷歌地图。当我们在谷歌地图中探索一个区域时,我们通常会先缩小视图以帮助我们获得上下文。一旦我们找到我们感兴趣的大致区域,我们可以放大以获得更多细节。
级别1:上下文
这个级别是最缩小的视图。它是系统在更广泛世界环境中的鸟瞰图。图表集中在参与者和系统上。
在下面的例子中,我们将使用一个简单的任务管理软件系统来演示这四个级别。
这个图表描绘了任务管理软件系统与外部系统的交互,以及使用它的不同用户群体。我们可以看到任务管理软件依赖于两个外部系统:电子邮件和日历,以及两种类型的参与者(用户)使用它:客户和管理员用户。
级别2:容器
容器级别是对系统的更详细视图(不要将C4容器与Docker容器混淆)。
它揭示了各种功能单元(如应用程序和数据库)如何协同工作并分配责任。
这个图表还强调了所采用的关键技术,并展示了这些容器之间的通信流程。它提供了系统核心组件及其交互的简化、以技术为中心的视图。
如果你使用微服务架构,那么每个微服务都将是一个容器。
容器的例子包括:
- 单页应用程序
- Web服务器
- 无服务器函数
- 数据库
- API
- 消息总线
- 等等
这个级别深入展示了任务管理软件系统的内部组成。它展示了我们的任务管理软件系统由诸如用户Web界面、管理员Web界面、API和数据库等容器组成。API也是连接到外部系统的容器,例如发送电子邮件或在日历中创建事件。
级别3:组件
下一级别的放大是组件。这显示了应用程序的主要结构构建块,通常是应用程序的概念视图。这里的"组件"一词比较宽泛。它可以代表一个控制器或包含业务逻辑的服务。
这个图表关注任务管理软件系统中API容器的内部结构。它揭示了API容器包含了数据操作的CRUD操作(创建、读取、更新、删除)和用户认证机制等关键功能。CRUD组件是与数据库交互的组件。
级别4:代码
最深层次的放大是代码图表。尽管这个图表存在,但通常不会使用,因为代码本身已经描绘了非常相似的图景。然而,在高度监管的环境和复杂的遗留项目中,这一级别可以帮助更好地描绘软件的内部复杂性。
补充图表
除了上述4个图表外,还有几个值得一提的:
- 部署图
- 动态图:描述过程或流程
登录流程
在这个图表中,我们展示了一个登录流程,它不是一个容器或组件,而是我们软件系统中发生的软件过程。它显示Web/管理界面使用基于JWT的认证与API通信,JWT令牌存储在客户端的本地存储中。
图表即代码
C4的强大之处在于采用了图表即代码的方法。这意味着像对待代码库一样对待你的图表:
- 版本控制:将它们存储在源代码控制系统(如Git)中,便于跟踪和协作。
- 协作:使用拉取请求共同处理图表,类似于代码审查。
- 自动化:将它们集成到你的构建管道中,使用你喜欢的工具自动渲染。
有用的工具:Structurizr
现在有几种工具可以帮助进行建模和绘图,但目前最流行的是Structurizr,它有自己的DSL(领域特定语言)。
你所需要的只是熟悉DSL语法,这相当简单。一旦你习惯了它,你将能够立即创建或更新图表。
下面你可以看到我们的任务管理软件系统的DSL。
ini
workspace {
model {
# Actors
customer = person "Customer" "" "person"
admin = person "Admin User" "" "person"
# External systems
emailSystem = softwareSystem "Email System" "Mailgun" "external"
calendarSystem = softwareSystem "Calendar System" "Calendly" "external"
# Task Management System
taskManagementSystem = softwareSystem "Task Management System"{
webContainer = container "User Web UI" "" "" "frontend"
adminContainer = container "Admin Web UI" "" "" "frontend"
dbContainer = container "Database" "PostgreSQL" "" "database"
apiContainer = container "API" "Go" {
authComp = component "Authentication"
crudComp = component "CRUD"
}
}
# Relationships (Actors & Systems)
customer -> webContainer "Manages tasks"
admin -> adminContainer "Manages users"
apiContainer -> emailSystem "Sends emails"
apiContainer -> calendarSystem "Creates tasks in Calendar"
# Relationships (Containers)
webContainer -> apiContainer "Uses"
adminContainer -> apiContainer "Uses"
apiContainer -> dbContainer "Persists data"
# Relationships (Components & Containers)
crudComp -> dbContainer "Reads from and writes to"
webContainer -> authComp "Authenticates using"
adminContainer -> authComp "Authenticates using"
}
}
让我们深入了解最重要的部分:
css
workspace [name] [description] {
model {
}
}
在这里我们定义我们的工作空间,它应至少有一个模型。工作空间可以选择给定一个名称和描述。
ini
customer = person "Customer" "" "person"
admin = person "Admin User" "" "person"
在这一部分中,我们按照以下格式定义我们的人员(例如,用户、参与者、角色或角色):person <name> [description] [tags]
。
你可以使用类似的格式(名称、描述、标签)来识别外部系统:
ini
emailSystem = softwareSystem "Email System" "Mailgun" "external"
calendarSystem = softwareSystem "Calendar System" "Calendly" "external"
要描述内部软件系统,我们需要编写一个块,还显示其容器和组件:
ini
taskManagementSystem = softwareSystem "Task Management System"{
webContainer = container "User Web UI" "" "" "frontend"
adminContainer = container "Admin Web UI" "" "" "frontend"
dbContainer = container "Database" "PostgreSQL" "" "database"
apiContainer = container "API" "Go" {
authComp = component "Authentication"
crudComp = component "CRUD"
}
}
容器格式:container <name> [description] [technology] [tags]
组件格式:component <name> [description] [technology] [tags]
模型的其余部分是最有趣的部分,在这里我们定义所有部分(系统、容器、组件)之间的关系:
rust
apiContainer -> emailSystem "Sends emails"
使用以下格式:<identifier> -> <identifier> [description] [technology] [tags]
。
Structurizr DSL还有其他可用的功能,如样式、主题、可见性等。你可以在这里找到它们。
在CI中自动渲染
由于你可以在GitHub上托管你的模型,因此很容易自动化管道来在你选择的工具中渲染图表。
在我们的例子中,Structurizr有一个GitHub Action,允许你运行structurizr-cli,这是Structurizr的命令行实用程序,它让你使用文本领域特定语言(DSL)基于C4模型创建软件架构模型。
这个示例仓库包含一个工作流,它只是生成一个静态页面并将其发布到GitHub Pages。
yaml
name: Deploy static content to Github Pages
on:
push:
branches: ["main"]
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: "pages"
cancel-in-progress: false
jobs:
build:
runs-on: ubuntu-latest
container:
image: ghcr.io/avisi-cloud/structurizr-site-generatr
options: --user root
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Create site
run: |
/opt/structurizr-site-generatr/bin/structurizr-site-generatr generate-site -w diagram.dsl
- uses: actions/upload-artifact@v3
with:
name: website
path: build/site
deploy:
needs: build
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v3
with:
name: website
path: build/site
- name: Setup Pages
uses: actions/configure-pages@v3
- name: Upload artifact
uses: actions/upload-pages-artifact@v1
with:
path: "build/site"
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v2
这个Github Action使用Structurizr CLI操作将我们的DSL文件编译为HTML并发布到Github Pages。
总结
我相信创建和维护有效且清晰的图表应该是轻松的。及时更新的可视化图表确保每个人都保持在同一页面上,消除混淆和浪费的时间。
C4模型和Structurizr DSL的一些自动化可以帮助使这个过程更快,并使图表与代码库保持一致。现在整个过程也可以自动化到你的SDLC中。
资源链接
译者注
本文翻译自Alex Pliutau的文章《How to Create Software Architecture Diagrams Using the C4 Model》,作为学习资料分享给大家。
作者简介:Alex Pliutau,BINARLY的高级软件工程师,Docker队长,专注于后端、Go、云和DevOps技术,同时编写packagemain.tech通讯。