架构守护代码化:架构文档即测试

架构守护代码化,即使用易于阅读和维护的领域特定语言,来描述软件架构守护的规则,对诸如于分层架构、包访问规则、包数量、继承命名等进行限制。

PS:我们这里所说的代码化,所指的是与领域特定语言的方式进行描述。

早先呢,我只是因为使用 Java 编写的 ArchUnit 不支持其它语言,而在其它语言的生态里呢,也没有这样的合适的工具。所以呢,我就想着在 Uncode 里设计一个全新的架构守护工具,也就是 Inherd 开源小组里的 Guarding: https://github.com/inherd/guarding/,一个多语言的架构守护工具 —— 基于 Tree Sitter 解析各类编程语言。它设计了一套外部 DSL,其借鉴于 ArchUnit 设计的内部 DSL 语法。

架构的腐化:为什么我们需要架构守护?

观察软件架构在开发过程中的变化, 是一件非常有意思的事情。日常,我经常与中大型规模公司的架构师、技术负责人聊天,讨论一些关于架构和规范相关的问题,也是颇有意思的。当我们聊到架构的时候,从聊天的过程来看,都是颇为美好的。但是呢,打开代码库,看到代码的分层实现、代码的一些规范,我们就会发现结果并非如此。

美好的开始。系统在设计的初期,架构师们都根据了自己的能力和经验,对系统进行了快速的“精心”的设计。随后,在迭代的开发中,按自己新捕获到的知识,对系统进行一些调整。如果这些资深的架构师都在编码(间歇性的),又或者是经常性的打开代码库瞧一瞧。那么,自然而然地系统不会出现过大的偏差。所以,我坚信对于有一定规模的软件组织来说,他们对系统都是有着良好的设计。

腐化的架构。系统在中后期开发的过程中,先前的架构师缺乏对于架构的关注,又或者是经历了一些人员的变更,导致了系统出现了一处又一处的架构不一致。当然,其中还有一类典型的原因是,架构相关的文档和规范缺乏了维护。由于这一系列的种种原因,使得我们看到的系统架构与这些架构师原先的预期是不一致的。

基于上述的种种原因,在架构上实施守护便成为诸多架构师要考虑的问题。

为什么需要架构守护代码化?

程序员讨论写文档,也讨厌别人没写文档。

对于架构知识的记载、传播和转换,也是知识传递的范畴。从当前阶段来看,它存在以下几个不同的级别:

  1. 系统本身没有架构文档,文档存在于人们的脑海里。
  2. 系统存在架构文档,难以理解(没有架构图)。
  3. 系统存在架构文档,只在早期创建,但与实际架构不一致。
  4. 系统的架构文档持续更新,但是未能及时反应问题。
  5. 系统的架构文档持续更新,并使用了架构守护,以确保两者的一致性。
  6. 系统的架构文档即系统的架构守护测试。

上述的几点,我想不论是开发者,还是架构师都是深有体会的。

文档化的架构,需要阅读和牢记

在架构设计初期,开发人员对于架构的设计往往都是经历过激励的讨论。所以,每个人对于架构的形态都掌握得差不多,不需要过多的记录也能知晓设计。沉淀下来的文案往往只是一些决策的结果,缺乏过程式的讨论等。

因为这一种种原因,所以在过去的几年里,我们一直在推崇『架构决策记录』(ADR),记录每一项架构上下文、决策和结果等相关的信息。

架构测试的局限性

这是一个老生常谈的问题,所以诸如于在 Java 世界里,人们设计出了 ArchUnit 这样在的工具来守护系统的架构。架构测试作为架构的文档,缺少易读性等等的问题。为了在多个项目中使用,还需要大量地复制和粘贴。

PS:在早期,我也尝试为 JavaScript / TypeScript 世界,设计类似的架构守护工具(即 dilay),但前端世界对于这一类的需要并不迫切。多年后,我又设计了一个新的工具,只是它已经适用于多个语言和框架。

架构守护即代码:架构文档即测试

架构守护代码化,即使用易于阅读和维护的领域特定语言,来描述软件架构守护的规则,对诸如于分层架构、包访问规则、包数量、继承命名等进行限制。

架构守护 DSL 示例

一个好的架构文档是个测试,并且可以执行。如 ArchUnit 设计的内部 DSL 语法:

classes().that().haveSimpleNameStartingWith("Foo")
    .should().resideInAPackage("com.foo")

这句话里,描述了一个规则:Foo 开头的类应该放在 com.foo 包下。这也是我们在设计架构的时候,会设计的架构文档。如果我们把它翻译成英语的话,它应该就是:

class(startsWith "Foo")) resideIn "com.foo"

另外一种潜在的形式可以是:

(startsWith "Foo").class resideIn "com.foo"

从实现难度上来说,两者的差别并不大,但是显然前者更易于理解和编写。以此类推,我们可以继续设计一系列的规则。

其它

欢迎加入 Guarding 架构测试与守护:https://github.com/inherd/guarding