GoSuda

Go 与 OpenAPI 生态系统

By iwanhae views ...

引言

在使用 Go 语言开发生产环境后端服务器时,几乎所有开发者最先遇到的难题之一便是如下问题。

API 文档化,该怎么做……?

针对这一问题稍加搜索便会发现,编写符合 OpenAPI 规范的文档是有益的,于是自然会去寻找与 OpenAPI 集成的库。然而,即便做出了这一决定,接下来仍会面临新的问题。

OpenAPI 相关的库太多了.. 该用哪个好呢……?

本文是为了那些正经历此类困惑的 Go 入门者而撰写的简要库介绍。本文以 2024 年末为基准编写,鉴于语言生态始终处于动态变化之中,建议在参考本文的同时,随时关注最新动态。

OpenAPI 面向下的各类库策略

想必您已经了解,OpenAPI 是用于清晰定义和文档化 REST API 的规范。它以 YAML 或 JSON 格式定义 API 的端点、请求与响应格式等,不仅有助于开发者,还能自动化前端与后端代码的生成,从而减少无意义的重复劳动,并在很大程度上降低细小的人为错误。

为了使 OpenAPI 与项目自然结合,Go 生态中的库大致采用以下三种策略。

1. 将 Go 注释组合为 OpenAPI 规范文档

在依照 OpenAPI 开发 API 时,一个棘手之处在于:实际文档与实现该文档的代码分别位于不同文件、不同位置,因此出现“更新了代码却忘记更新文档”或“更新了文档却未能更新代码”的情况相当常见。

举一个简单的例子:

  1. 在 ./internal/server/user.go 文件中修改了 API 的逻辑;
  2. 而实际文档位于 ./openapi3.yaml,可能会不慎忘记对其进行相应变更;
  3. 若在未察觉该问题的情况下提交 Pull Request 并交由同事审阅;
  4. 审阅者同样无法在 ./openapi3.yaml 中看到变更,因此可能发生 API 规范保持不变、而实际 API 实现已发生变化的不幸情形。

以 Go 注释的形式编写 API 文档,可在一定程度上解决上述问题。代码与文档汇集于同一处,因此在修改代码的同时即可一并更新注释。基于这些注释自动生成 OpenAPI 规范文档的工具也已存在。

具有代表性的项目是 Swag。Swag 解析 Go 代码中的注释,并生成 OpenAPI 2 格式的文档。其使用方法十分简单:只需在处理函数上方,按照各库规定的格式编写注释即可。

 1// @Summary 유저 생성
 2// @Description 새로운 유저를 생성합니다.
 3// @Tags Users
 4// @Accept json
 5// @Produce json
 6// @Param user body models.User true "유저 정보"
 7// @Success 200 {object} models.User
 8// @Failure 400 {object} models.ErrorResponse
 9// @Router /users [post]
10func CreateUser(c *gin.Context) {
11    // ...
12}

如此编写注释后,名为 Swag 的 CLI 工具会解析这些注释并生成 OpenAPI 2 文档。通常此项工作在 CI 流程中执行,生成的 OpenAPI 规范文档随后会被发布至 Git Repository、最终构建产物或独立的外部 API 文档管理系统,以供与其他项目协作时使用。

优点:

  • 注释与代码同处一处,因此实际代码与文档的形态产生偏差的可能性降低。
  • 无需额外工具或复杂配置,仅凭注释即可简便且灵活地完成文档化。
  • 注释不会影响实际的 API 逻辑,因此便于添加那些不宜公开文档的临时功能。

缺点:

  • 随着注释行数增加,单个代码文件的可读性可能下降。
  • 仅以注释的形式,可能难以表达全部 API 规范。
  • 文档并不能强制约束代码,因此无法保证 OpenAPI 文档与实际逻辑一致。

2. 由 OpenAPI 规范文档生成 Go 代码

也存在另一种方法,即将 Single Source of Truth (SSOT) 置于文档而非 Go 代码之中。具体而言,即先定义 OpenAPI 规范,再基于已定义的内容生成 Go 代码。由于 API 规范本身即可生成代码,因此能够在开发文化上强制先进行 API 设计;同时,由于开发顺序以定义 API 规范为起点,便能及早发现遗漏之处,防止在开发完成之后才发觉问题并连同 API 规范一起修改整体代码这类不幸情形,这是该方式的一大优势。

采用这一方式的代表性项目有 oapi-codegen 与 OpenAPI Generator。其使用方法同样简单:

  1. 按照 OpenAPI 规范编写 yaml 或 json 文档;
  2. 运行 CLI;
  3. 即会生成对应的 Go stub 代码;
  4. 此后只需直接实现各个 API 的具体逻辑,使该 stub 得以运作即可。

以下是 oapi-codegen 所生成代码的示例:

1// StrictServerInterface represents all server handlers.
2type StrictServerInterface interface {
3	// ...
4	// Returns all pets
5	// (GET /pets)
6	FindPets(ctx context.Context, request FindPetsRequestObject) (FindPetsResponseObject, error)
7	// ...
8}

以上述 interface 为媒介,oapi-codegen 生成的代码负责执行 query parameters、header、body 的解析及 Validation 等逻辑,并调用 interface 中声明的相应 method。使用者只需实现该 interface 的具体实现体,即完成了 API 实现所需的全部工作。

优点:

  • 规范先行、随后进行开发,因此在多团队协作的情况下,便于并行推进工作。
  • 原本属于重复性的手工劳动的部分代码会自动生成,因此工作效率得以提升,同时调试依然十分便利。
  • 易于保证文档与代码的形态始终一致。

缺点:

  • 若对 OpenAPI 规范本身不熟悉,初期学习曲线会有一定的陡峭。
  • 由于处理 API 的代码结构由项目自动生成,因此在需要定制化时,可能难以应对。

作者注。截至 2024 年 10 月,OpenAPI Generator 所生成的 Go 代码不仅强制规定 API 逻辑,还规定了整个项目的结构,其结构僵化,不适合用于添加生产环境所需的各类功能。采用该方式的读者,强烈建议使用 oapi-codegen。作者本人使用的是 oapi-codegen + echo + StrictServerInterface。

3. 由 Go 代码生成 OpenAPI 规范文档

当数十、数百人同时对同一服务器进行开发时,必然会出现的问题是:各个 API 之间的一致性可能被打破。直观的例子是,若将 100 多个 API Endpoint 的规范全部声明在一个 OpenAPI yaml 文件中,该文件必将膨胀为超过一万行的庞然大物;在新增 API Endpoint 时,难免会重复声明相同的模型,或遗漏某些字段,或出现不符合约定的 Path 命名,整体 API 的一致性由此开始瓦解。

为解决这些问题,可以指定专门负责管理 OpenAPI yaml 的 Owner,或开发 Linter 并在 CI 流程中自动检测;但也可以使用 Go 语言定义 Domain-specific language (DSL),从而强制所有 API 保持统一的一致性。

采用这一技术的代表性项目是 Kubernetes(自行构建,未使用额外库)。此外,也可以使用 go-restful、goa 等项目。以下是 goa 的使用示例:

 1var _ = Service("user", func() {
 2    Method("create", func() {
 3        Payload(UserPayload)
 4        Result(User)
 5        HTTP(func() {
 6            POST("/users")
 7            Response(StatusOK)
 8        })
 9    })
10})

如上所示,编写可编译的 Go 代码后,POST /users API 的实现与文档定义便可同时完成,这是其显著优势。

优点:

  • 一切均由代码生成,因此便于在整个项目范围内保持 API 的一致性。
  • 借助 Go 的强类型系统,相较于充分利用 OpenAPI3 全部功能的情形,能够获得更加精确且无争议的规范。

缺点:

  • 需要掌握各框架所定义的 DSL,且较难应用于既有代码。
  • 由于必须强制遵循框架所提出的规则,自由度与灵活性可能下降。

结语

各种方法各有优劣,根据项目需求与团队偏好选择合适的方法至关重要。最重要的并非哪种方式更好,而是对自身所处的情境进行价值判断,明确何种方案最为适合,并提升开发效率,从而享受准时下班与令人满意的工作与生活平衡。

本文以 2024 年 10 月为基准撰写,但 Go 与 OpenAPI 生态仍在持续发展。请考虑阅读本文时的时间间隔,持续跟进各库与项目的最新动态及其变化后的优缺点。

祝您 Go 生活愉快~ 😘