Go中自动生成的C4架构图(附代码示例)

311 阅读6分钟

你好!请给Krzysztof一个热烈的欢迎,这是我们博客上的第一篇客座文章。🎉我们与Krzysztof合作了两年,我们很高兴能在这里分享他的作品。

我们都在为软件架构图挣扎,不是吗?你有没有想过为什么?如果你问自己这个问题,为什么维护最新的、详细的软件架构图是如此痛苦,你会得出一长串有效的答案。

我们的软件一直在变化。我们每天都在更新,从简单的改名开始,到核子重构结束,重塑整个应用。每一个变化都需要对软件架构图进行仔细的调整。最有可能的是,你也曾认为用最方便的工具画图是很耗时的。它需要大量的努力。那些图出了名的失控,储存了过时的命名和已经丢弃的模块,同时缺少了最初的可读性和良好的组织结构。所有这些都使人感到特别沮丧。

如果一个理想的世界曾经存在...

当加入一个新的、未经探索的项目时,每个人都会欣赏一个结构良好的图示。如果它是一个易于浏览的、类似地图的C4模型图,那就更好了。

自动生成的C4图例

如果这个概念对你来说并不熟悉,它背后有一个简单的想法。C4模型引入了软件架构可视化的四个层次。上下文、容器、组件和代码。根据你所寻找的信息,你可以深入到实施的具体部分,就像你查看地理地图一样。当搜索一个特定的地址时,你通常会使用一个城市甚至一个地区的地图,而不是整个国家的地图。因此,当你检查单个服务模块如何相互交谈时,你会在该服务Components 层的图中找到它。当你寻找服务和运行时的相互作用时,你会去找Containers 。你明白了吗?欲了解更多信息,请到C4模型网站。在我看来,这是迄今为止提出的最好的方法。

维护详细的、最新的应用程序架构图是否值得花费这么多精力?而且必须要手动完成吗?如果我们可以...

将其自动化!

如果你在Go生态系统中工作,你已经习惯了代码生成。如果我们也能从代码中生成软件图呢?此外,如果我们能在持续集成管道内自动完成这个工作呢?

在这个想法的驱动下,我写了一个库,只用一个配置文件就能从代码中生成图表。让我告诉你如何使用它。

让我们从简单的开始

为了演示这个功能,我选择了一个大多数读者都熟悉的资源库--wild-workouts-go-dd-example应用程序。有几个服务,我们可以尝试为其生成图表。让我们从trainer 这个开始。

目前,该库提供了一套组件来编码你自己的图表自动生成命令。

我在wild-workouts-go-ddd-example 仓库里做了这个。在一个单独的tools/c4 目录中,我创建了一个简单的命令文件:

func main() {
	s, err := scraper.NewScraperFromConfigFile("scraper.yml")
	if err != nil { ... }

	app := trainerService.NewApplication(context.Background())
	
	structure := s.Scrape(app)

	v, err := view.NewViewFromConfigFile("scraper.yml")
	if err != nil { ... }

	outFile, err := os.Create("out/view.plantuml")
	if err != nil { ... }
	defer outFile.Close()

	err = v.RenderStructureTo(structure, outFile)
	if err != nil { ... }
}

在这个命令中,我使用了库中提供的几个组件。

第一个,scraper ,按照提供的配置和搜刮规则,抓取任何Go结构并收集被访问的组件。我从一个最小的配置文件开始:

configuration:
  pkgs:
    - "github.com"

rules:
  - name_regexp: ".*"
    pkg_regexps:
      - ".*"

在配置中,我指示搜刮器抓取任何以github.com 为前缀的软件包中的所有组件,然后收集符合所提供的名称和软件包正则表达式规则的组件。表达式.* 将匹配任何字符串,所以基本上,我想捕获所有的东西。

对于那些讨厌YAML的人来说,同样的配置也可以通过编程完成。

接下来,我需要将服务应用上下文实例化,作为要搜刮的 "内容"。Robert和Miłosz已经实现了一个方便的构造函数,我在这里调用了它。然后,我可以把它传递给搜刮器,并获得返回的Structure ,其中包含所有收集的数据。

我实例化了另一个库组件,将获得的结构渲染到输出文件中--view 。它消耗的是与搜刮器相同的配置文件,只用一行视图定义就可以增强:

view:
  title: Trainer service components

...然后我渲染它。

输出文件的类型是*.plantuml。我需要用plantuml CLI工具把它渲染成一个*.png的图片文件:

 plantuml out/view.plantuml

这是我得到的东西:

原始C4图

变得更有趣

到目前为止,一切都很好。但是这里至少有几个问题。它可以工作,但都是平面的我在图中看到的只是一张没有任何细节、描述或颜色编码的组件地图。哪些组件是属于应用层或领域层的?另外,我们对周围的基础设施了解不多。是否使用了任何数据库?如果有,哪些组件在使用它?让我们来迭代一下。

我需要通过添加带有firestore客户端的包来扩展搜刮范围。这个应用程序使用了firestore数据库,所以我在图中包括了它的客户端:

configuration:
  pkgs:
    - "github.com"
    - "cloud.google.com/go/firestore"

然后,我创建了几个规则,指示搜刮器解释来自指定软件包的组件。通过规则,我可以对组件进行标记,添加描述,定义名称功能,以及更多。

这里有几个规则的例子,我定义了应用程序、领域和数据库组件:

rules:
  - name_regexp: ".*"
    pkg_regexps:
      - ".*/app/command.*"
    component:
      description: "application command"
      tags:
        - APP
  - name_regexp: ".*"
    pkg_regexps:
      - ".*/domain/.*"
    component:
      description: "domain component"
      tags:
        - DOMAIN
  - name_regexp: ".*Client$"
    pkg_regexps:
      - "cloud.google.com/go/firestore$"
    component:
      name: "Firestore"
      description: "firestore client"
      tags:
        - DB

最后,我可以添加一些视图样式,并将其分配给上面定义的标签:

view:
  title: Trainer service components
  line_color: 000000ff
  styles:
    - id: APP
      background_color: 1a4577ff
      font_color: ffffffff
      border_color: 000000ff
    - id: DOMAIN
      background_color: ffffffff
      font_color: 000000ff
      border_color: 000000ff
    - id: DB
      background_color: c8c8c8ff
      font_color: 000000ff
      border_color: 000000ff
      shape: database

当我重新运行搜刮器时,我得到了以下结果。现在看起来是不是好多了?

风格化的C4图