Blog | Phodal - A Growth Engineerhttp://www.phodal.com/blog/2021-06-15T12:33:44+00:00Blog架构守护代码化:架构文档即测试2021-06-15T12:33:44+00:002021-06-15T12:14:35.914125+00:00Phodal Huanghttp://www.phodal.com/blog/author/root/http://www.phodal.com/blog/arch-guard-as-code-architecture-document-as-test/> 架构守护代码化,即使用易于阅读和维护的领域特定语言,来描述软件架构守护的规则,对诸如于分层架构、包访问规则、包数量、继承命名等进行限制。
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 语法:
```java
classes().that().haveSimpleNameStartingWith("Foo")
.should().resideInAPackage("com.foo")
```
这句话里,描述了一个规则:`Foo` 开头的类应该放在 `com.foo` 包下。这也是我们在设计架构的时候,会设计的架构文档。如果我们把它翻译成英语的话,它应该就是:
```bash
class(startsWith "Foo")) resideIn "com.foo"
```
另外一种潜在的形式可以是:
```
(startsWith "Foo").class resideIn "com.foo"
```
从实现难度上来说,两者的差别并不大,但是显然前者更易于理解和编写。以此类推,我们可以继续设计一系列的规则。
## 其它
欢迎加入 Guarding 架构测试与守护:[https://github.com/inherd/guarding](https://github.com/inherd/guarding) 。流程即代码:低代码 & 云研发 IDE —— Uncode2021-03-28T12:30:50+00:002021-03-28T12:10:59.959073+00:00Phodal Huanghttp://www.phodal.com/blog/author/root/http://www.phodal.com/blog/process-as-code-low-code-ide-uncode/在先前的一系列《[云研发:研发即代码](https://github.com/phodal/cloud-dev)》文章里,我们介绍了软件工程的代码化闭环。同时,在《[Water:云研发架构模式](https://github.com/phodal/water/)》介绍了设计这样的开发环境里,我们所需要的一些模式。今天呢,作为这一系列的落地实践,我们将介绍**云研发 IDE**的设计思想,以及如何实现,当然还有一点儿早期代码:[https://github.com/inherd/uncode](https://github.com/inherd/uncode)。
第一次声明:这是一个概念性 IDE 的设计,暂不适合任何生产环境。
在开始真正阅读之前呢,为了能更好地让大家理解,我们要回顾一下软件工程行业:
- **DevOps 理念**在国内的软件行业有了长足的发展,在包括传统企业(银行、制造业)在内的公司里已经广泛接受,并进行了大规模推广。
- **云原生技术**已经成为市场的主流趋势。云迁移与遗留系统上云是市场的一大热门话题。
- **中台方法论**在实践上还缺少真正的成功案例。
- **低代码/无代码平台**逐渐成为新的建设目标。
- **云开发**有了越来越多的中小规模应用案例。
- **AI 生成代码**正在被小范围验证。
从整个行业而言,人们的关注点一直是**如何提升技术生产力?** 现在技术到了一个新的阶段了,而需求的转换大大限制了人们的开发速度。于是无论我们的 DevOps 和云开发实施得再好,也会陷入需求与技术隔离的瓶颈。这就是为什么我们需要[云研发](https://github.com/phodal/cloud-dev) 理论体系 :),通过代码化的方式,一站式解决需求到设计,再到代码的问题。
对于云研发理论来说,我已经设计好了理论基础、软件架构、开发模式,并且对其中的一系列东西进行了验证,如:[文档代码化](https://www.phodal.com/blog/document-as-code/)、[需求代码化](https://www.phodal.com/blog/requirement-as-code/)、[代码的代码化](https://www.phodal.com/blog/charj-lang/) 等。
我们需要一个容器,把这些内容、模式、代码整合到一起,这就是 Uncode,一个概念性的云研发 IDE。
# Uncode,一个云研发 IDE
Uncode 是一个面向云研发时代设计的下一代概念性 IDE。特性:
- 流程化为领域语言。Process as code
- 一切皆 DSL。万物代码化
- 填空式/选择式编程。
- 开发环境即流程。
简单来说,你可以在这个 IDE 上完成:需求的编写,转换需求为设计,设计关联代码,禅模式编程,开发完即可上线。
与之相对比的是,传统的一站式 DevOps 门户,尽管你可以通过跳转来完成,但是无法相互关联和设计。与之相近的是 GitOps,即将应用系统的声明性基础架构和应用程序存放在 Git 版本库中。但是它们都不闭环,也不完整。
## 云研发 IDE 模式:流程即领域语言
回到软件开发上,我们的软件开发需求始于一个大特性或者史诗故事,这些故事会转换为一个 `feature`,如 Cucumber 中的:
```gherkin
# author: Phodal HUANG
# status: doing
# language: zh-CN
功能: 第一个用户故事
场景打开 Uncode
假如我在 Terminal 工具里
当输入 uncode
那么则能在 Uncode IDE 里打开当前项目
```
需求设计人员在这一步之前,将需求转换为了故事,故事与特性之间的关系记录在这个 `feature` 中。开发人员从 IDE 中看到需求,标记了对应的状态 `status`,就可以进入代码的设计阶段。
在设计这个阶段,我们先设计了 `design` 的三种类型:`flow`、`model`、`ui`,对应于流设计、模型设计和 UI 设计。而我们要在 Uncode 中实现的部分便是需求与模型、流和 UI 的绑定。围绕模型,我们还得构造统一的领域语言,用于自动化关联接口与设计。从模式上来说,这个和无代码/低代码的开发是相似的。
唯一不同的是描述方式。使用领域特定语言来描述内容,我们才能对系统进行合理地重构。
## 云研发 IDE 模式:一切皆文件
> Linux/Unix下的哲学核心思想是『一切皆文件』。
在现今的开发环境之下,我们在看板上挑选卡片,又或者是通过低代码编辑器生成,使用的存储介质都是数据库。而数据库这些东西并不存在于开发环境中,而是放置于远程服务器上。这就造成了另外一个痛点,无法简单反向关联、需求与代码隔离等等。
于是,作为云研发 IDE 的第二个模式,将所有的内容使用文件保存,并且使用版本管理工具(如 Git)进行管理。如我们的需求以类似于代码的形势存储在数据库中,可以实现以下特性:
- “不可伪造”
- “全程留痕”
- “可以追溯”
- “公开透明”
- “集体维护”
没错,这就是一个区块链系统。一旦需求发生了变化 ,你可以即刻感知到。不过,一旦你的代码与模型不相符合,你的代码就无法提交,或者模型被自动修改 :(。
## 云研发 IDE 模式:开发环境即流程
作为一个集成开发环境,现有的 **一站式 DevOps 软件研发管理协作平台** 都应该只被当作管理和展示用途。而从设计本身来说,一个 Dashboard 和一个开源工具,本身就分工。
我们在代码库上有了需求,那么我们可以借助于 IDE:
1. 将需求以看板的形式在本地重新可视化出来。
2. 将设计领域的语言在本地可视化出来,并将之与代码进行关联。
3. 高亮需要所有修改的代码块。如 Controller、View 等。
4. 将模型的修改反向关联到设计上,以实时追踪设计的正确性。
我们还可以做一些不那么正确的事情 ,如锁定开发人员的修改范围。
## 云研发 IDE 模式:填空式/选择式编程
对于软件架构师来说,人们经常有这么一些痛点:
- 面对的是缺乏经验的开发者,难以快速地推进系统的开发。
- 开发者缺乏对系统的了解,在错误的地方修改错误的代码。
因此,回到 [TypeFlow](https://github.com/notyy/TypeFlow) 的观点上,我们既然已经设计好了模型,设计好了输入和输出,那么我们一定能生成中间的方法及其返回值,并为其设计一个 mock 的对象。如:
```java
@RequestMapping("/")
String home() {
return "Hello, World!"
}
```
这种模式对于业务应用开发来说,非常易于实现 —— 生成绑定过程中的各类函数等等。
**选择式编程**。而一旦我们在组织内的所有代码都被索引之外,我们有能力通过识别输入和输出,以及对应的方法名,就能在 IDE 中推荐对应的方法让你选择。
# 云研发 IDE 基础要素
就这么一看,我们只需要搞好 IDE 的事情即可。然而, 并非如此,我们还要做的事情还有一些:
1. 开发即部署。即 local dev 便是 dev server,可直接接入现有的系统。
2. 万物即 DSL。具备一定等级的程序语言设计能力。
3. API 的 API。即将现有的内部、外部 API 进行抽象化设计,以提供快速可用的 API。
## 开发即部署 —— 云开发环境
从开发层面来看,我们一直在往复地浪费本地环境和线上开发环境,与此同时还有对应的测试运行时间、构建时间等。我们需要一个于**云开发环境**的机制。
**加速联调、测试过程**。当我们的本地环境上云之后,一旦需要与其它系统对接时,所有的开发、测试效率将大大提升。譬如说,我们的接口需要多提供一个参数,传统模式之后,我们要在本地运行,再通过流水线构建和部署。而现在,不再需要这个过程了,只需要配置好 Gateway,轻轻松松进行开发。
**加速环境搭建**。我们不再需要在本地配置开发环境,只需要 1-click 就可以在本地 IDE 里直接调试。
市面上已经有一个勉强配合的概念:[Nocalhost](https://github.com/nocalhost/nocalhost)
## 抽象的抽象:DSL
对于需求、设计、开发、测试等的抽象,一直是我在去年研究的重点,它包含了:
- 需求的抽象
- 设计化为抽象
- 架构描述语言
- 统一建模语言
- 版本管理抽象
- 构建工具抽象
即将这一系列的步骤转换为领域特定语言 —— 只有将流程、工具、行为进行抽象,我们才得以优化整个系统。
## 胶水设计:API 的 API
软件开发是一项复杂的团队活动。在一个系统里,我们要与大量的内、外部系统进行关联。而为了简化开发人员的负担,我们需要提供一个新的 API 来将现有的 API 进行封装。
如在现有的模式之下,为了记录一个日志,我们需要在依赖管理工具中引入对应的依赖,再添加相当的代码。而所有的 API 都是在更新的,这一系列应该将由 IDE 本身来完成。在这种模式之下,我们只需要输入对应的 `snippets`,便能完成这一系列的自动化过程操作。
# 技术细节
最后,我们还是回到代码上:[https://github.com/inherd/uncode/](https://github.com/inherd/uncode/)
## 架构设计
我决定使用我设计的新架构设计套路来展示一上 Uncode IDE 的架构。由于不确定性较大,现有的系统是一种介于单体与微架构 + 模块化的方式设计的,我想了想后来就称之为**流体模式**。一种在持续演进的过程中,不断进行不可预料地拆分架构单元的模式。
在驱动方式上,由四种模式构成:
- 模块化。
- 管理和过滤器。主要进行领域特定语言的设计
- 搭档模式(sidecar)。将诸如语言解析等独立为进程,通过进程调用来实现跨平台
- 容器桥。将 UI 展示与逻辑相隔离,让 IDE 的大部分组件与 UI 无关。
同时系统的物理设计上,打算采用**领域驱动**的方式进行。
## 框架选型
考虑到这是底层开发 + 系统编程,我们:
1. **使用 Rust 来作为主要开发语言**
2. 在 UI 展示上,暂时使用 Tauri(WebView 容器) + React 来展示需求(本地看板)与设计(建模等)。
3. 使用 TypeScript 作为 UI 部分开发语言
4. 使用 RPC 作为与多个 DSL 的通信协议
5. ……
依旧地,这个项目将继续在 Inherd 小组上开发~~。
# FAQ 及其它
代码:[https://github.com/inherd/uncode/](https://github.com/inherd/uncode/)
## vs Intellij IDEA or VSCode / Theia
并非完全竞争关系,编码这部分的功能,还是这两货比较流行。Uncode 不会在前期造这方面的轮子,只是显式地集成它们,或者被集成。
Uncode 优先解决 DevOps 的本地化,将其融入开发的开发过程的问题。
## 其它
最后一次声明:这是一个概念性 IDE 的设计,暂不适合任何生产环境。