C4

C4 可视化架构设计

软件系统架构设计的目标不在于设计本身,而在于架构设计意图的传达。图形化有助于在团队间进行高效的信息同步,但不同的图形化方式需要语义一致性和效率间实现平衡。C4 模型通过不同的抽象层级来表达系统的静态结构,并提供了最小集的抽象建模元素,为设计人员提供了一种低认知负载、易于学习和使用的高效建模方式。该工具的作者在多年的咨询中经常发现,很多个人画出来的架构图都是不一样的,但也不是说他们谁画错了,而是每个人的抽象层次不一样。抽象层次这种东西,说起来好像存在,但真要说清楚还挺难,于是作者类比地图,提出了缩放的概念。

地域环境缩放

上面的四张地图就是想说明,当我们看待真实世界的“架构图”的时候,也是要不停的缩放,在每一个层次刻意忽略一些细节才能表达好当前抽象层次的信息。所以他类比着把架构也提出了四个抽象层次:

C4 层次

从上到下依次是系统 System、容器 Container、组件 Component 和代码 Code。

四张核心图

系统上下文图

系统上下文图

如上图所示,这个图表达的是你所开发的系统和它的用户以及它所依赖的系统之间的关系。从这个图上我们已经看出来 C4 图形的几个关键图形:

C4 关键图形

C4 说穿了就是几个要素:关系——带箭头的线、元素——方块和角色、关系描述——线上的文字、元素的描述——方块和角色里的文字、元素的标记——方块和角色的颜色、虚线框(在 C4 里面虚线框的表达力被极大的限制了,我觉得可以给虚线框更大的扩展空间)通过在不同的抽象层次上,重新定义方块和虚线框的含义来限制我们只能在一个抽象层次上表达,从而避免在表达的时候产生抽象层次混乱的问题。

那么在系统上下文图里,方块指代的是软件系统,蓝色的表示我们聚焦的系统,也就是我开发的系统(也可能是我分析的系统,取决于我是谁),灰色表示我们直接依赖的系统,虚线框表示的是企业的边界。通过这些图形化的元素表达我们可以看出来各个系统彼此之间的关系。

容器图

容器图

当我们放大一个系统,就会看到容器,如上图所示,C4 模型认为系统是由容器组成的。容器是我个人认为,C4 模型最大的创举,尤其是在这个单体架构快速崩塌的时代。所谓容器,既不是 Docker 的容器,也不是 JavaEE 里的容器,是借用了进程模型,每一个容器都是指有自己独立的进程空间的一种存在。不管是在服务器上的单独进程空间,还是在浏览器里的单独进程空间,只要是单独的进程空间就可以看作一个容器。当然如果你容器化做得好,Docker 的 Container 和这个 Container 可以一一对应。有了这个概念的存在我们就可以更清晰的去表达我们的架构,而不是总是用一些模糊的东西。

组件图

组件图

当我们放大一个容器,我们就会看到组件,如上图所示。组件在这里面很好的把接口和它的实现类打包成一个概念来表达关系。我个人觉得有时候一些存在于代码中,但又不是接口的某些东西,比如 Service、Controller、Repository 之类也可以用组件图来表达,如果你学了一些没有明确抽象层次的架构知识或者一些单体时代的遗留经验的时候,你可以画出来一些组件图,来印证自己的理解,如下图,我画的我对 DDD 战术设计里面的一些概念的理解:

组件图

代码图

代码图没什么可说的,就是 UML 里的类图之类很细节的图。一般是不画的,都是代码生成出来。除非非常重要的且还没有写出代码的组件才画代码图。

以上就是 C4 的核心图,我们可以看到四种不同的抽象层次的定义会让我们更容易固定住我们讨论的层次,这点上我觉得 C4 是非常有价值的。

三张扩展图

架构设计设计要考虑的维度很多,仅四张核心图是不够的,所以作者又提供了三张扩展图,可以让我们关注更多的维度。

系统景观图

系统景观图

和它的直接关系,连一些间接相关的系统都会标示出来,那些系统的用户以及用户之间的关系也会标示出来,只是内部的用户会用灰色标记。

这个图有什么用呢?在我们分析一个企业的时候,我们需要一个工具帮助我们把一家公司给挖个底掉,做到完全穷尽,才能看到企业的全景图从而理解局部的正确定位以做好局部设计为全局优化服务。之前我试过以四色建模的红卡、事件风暴的事件两种工具教人去掌握这种能力,当学员是程序员的时候都无法快速掌握这种顺藤摸瓜的分析技巧,毕竟跟程序员的思维还是有些差异的。但是我用了系统景观图之后,学员毫不费力的就掌握了这种分析能力。所以我后来都是用这个图来教程序员探索企业的数字化全景图,效果极好,推荐给大家。

动态图

动态图

动态图不同于其他图都是表达静态关系的,它是用来表达动态关系的,也就是不同的元素之间是如何调用来完成一个业务的。所以动态图不仅仅在一个层面上可以工作,它在系统级、容器级和组件级都可以画,表达的目标是不一样的。

我之前曾经写过名为《像机器一样思考》的一系列文章,里面我也发明了类似的图,不同于他的关系线上标注的是调用的方法、函数,我更关注的是数据,使用效果也很好。

什么时候是用动态图呢?举个小例子,我之前做一个内部的小系统,只有一个有经验的工程师带着 10 多个毕业生,我便要求他们在开始工作之前都画出动态图来,交由有经验的工程师去评估他们的思路是否正确,如果有问题,就在开始之前就扼杀掉了烂设计。不管是毕业生还是初级工程师,改代码的能力都比写代码的能力要差很多,所以将烂设计扼杀在实现之前还是有帮助的。

部署图

部署图

前面的几张图都是站在开发的角度思考,但是一个没有充分思考过部署的架构很容易变成一个运维的灾难。所以作者提供了一个部署图。考虑到 DevOps 运动如火如荼,这个图可以变成很好的 Dev 和 Ops 之间沟通的桥梁。我们在实操中发现,Dev 和 Ops 关注点的不同、语言的不一致,在这张图上表现得非常清楚。

图上最大的的实线框不同于虚线框,它表达的是数据中心,当你开始考虑异地载备的时候它就有了意义。数据的同步、实例的数量都会影响你部署图的内容。部署图基本都是容器级的,它会很好的表达出来容器到底部署了几个实例,部署在什么样的操作系统上,一个节点部署了几个容器之类,我们在实际使用中,发现需要考虑的信息太多,自己就抽象出了类似于亚马逊上实例规格的 Small、Large 之类的术语来表达机器配置,增进了开发和运维之间的交流准确性。

Links