Swagger+OpenAPI Generator快速生成多语言SDK

引言

为了将自己的服务提供出去供第三方使用,往往需要把服务以标准的 OpenAPI 形式暴露,并提供多语言版本的 SDK(软件开发工具包),比如常见的环信即时通讯服务、飞书开放服务、微信开放服务等都是基于 SDK 来让开发者调用和接入的。那么如何把我们自己写的 HTTP 服务转换成 OpenAPI 的标准格式,再包装成便捷的 SDK 呢?最近我基于 Swagger 和 OpenAPI Generator 两个利器进行了一些实践。

Swagger

随着前后端分离开发模式的普及,前端已经全权承担了静态资源的管理,而后端也只用专心于提供接口,前后端之间通过传输载体(通常为 JSON)进行交互。既然需要交互,那么前端必然需要知道后端的接口地址、入参、出参、参数位置等定义,才能准确包装好请求体(通常为 HTTP)。

那后端如何提供接口定义呢?常用的方式可以通过写文档 或 Swagger UI。写文档的方式比较机械,如果代码里的接口定义发生修改,则要同步记得更改文档。相比之下 Swagger 会友好得多,Swagger 是一个可以生成、描述、调用、可视化 Restful 风格服务的框架,它里面的组件很多,但我们最常用到的,就是在我们的代码里面引入 Swagger 的库或者插件,然后通过在 HTTP 服务上打注解(或写注释),然后一键生成 swagger.json 文档,该文档是符合 OpenAPI 规范的,并且会启动 Swagger UI 服务,提供可视化的页面供前端查看。下面是使用 Golang 语言提供的一个 HTTP 服务,服务上方被打上了 Swagger 可以识别的注释,并引入了 github.com/swaggo/gin-swagger 库来生成 Swagger 文档和页面:

import (
   "demo/request"
   "demo/response"
)

// @Summary    创建用户
// @Users      user
// @Accept     json
// @Produce    json
// @Param      object      body        request.CreateUserRequest    true  "创建用户参数"
// @Success    200         {object}    response.Response[CreateUserResponse]
// @Router     /users      [post]
func (h *UserHandler) Create(ctx *gin.Context) {
    // parse request 
    ...
    // validate request
    ...
    // invoke service
    ... 
    // return response
    ...
}

可以看到我们只需向 gin 服务的入口函数上写上这个 HTTP 接口的定义即可,需要说明的是 @Param 和 @Success 由于用到了 request 和 response 包下的结构体,Swagger 要求必须引入这些结构体所在的包才能完成解析。CreateUserRequest, CreateUserResponse, Response 结构体的定义如下:

package request

type CreateUserRequest struct {
   Username string `json:"Username"`
   Password string `json:"Password"`
   Role     string `json:"Role"`
}
package response

type Response[T any] struct {
    Code    int     `json:"Code"`
    Message string  `json:"Message"`
    Data    T       `json:"Data"`
}

type CreateUserResponse struct { 
    UserId string `json:"UserId"`
    Status int    `json:"Status"`
}

需要说明的是,因为每个响应结果除了要返回数据本身,往往还要返回 Code 和 Message 标识本次请求在业务语义上结果,因此这里引入了 Response[T any] 泛型类进行装饰,那么相应的,上面 Swagger 注释也需要写成这样 response.Response[CreateUserResponse]。最后我们还需要使用安装命令 swag 命令行工具来生成 Swagger 文档,像下面这样:

# install
go get -u github.com/swaggo/swag/cmd/swag
go install github.com/swaggo/swag/cmd/swag@latest

# copy swag from $GOPATH to /usr/local/bin
cp $GOPATH/bin/swag /usr/local/bin

# generate swagger.json
swag init -g ./cmd/server/main.go -o ./cmd/server/doc --parseDependency

执行完上面生成命令后,swagger.json 便会出现在我们的目录下:

.
├── cmd                    
│ ├── server           
│ │ ├── docs              
│ │ │ ├── docs.go  
│ │ │ ├── swagger.yaml           
│ │ │ └── swagger.json      
│ │ └── main.go
...

gin-swagger 注释的语法还有很多,以及如何提供 Swagger UI 静态页面服务,都可以参考这里,本文不再赘述。

OpenAPI Generator

其实以前我用 Swagger 也就到上面为止了,更多的是去使用 Swagger UI 查看接口,至于 swagger.json 为何物完全不关心,另外如果要调用 HTTP 接口,直接基于 HTTP 通信库填好URL 地址、Header、入参、令牌就好了,为什么还需要 SDK 呢?其实直接调用 HTTP 通信库一点问题都没有,但这样会增大接入方的研发成本,他需要手工拼接好接口的参数,字段还不能有拼写错误,另外如果参数有签名还要关心用什么签名算法,因此 SDK 就是为了给接入方减负出现的,因为它不再要求接入方关注过多细节,而通过编程语言的函数封装、函数签名、结构体/类都约束好了,如果开发者接口或者字段拼写有错误,那么在程序编译的时候就会报出来,风险相当于从服务器端上移到客户端了。

那我们如何才能更方便的生成 SDK 呢?上面提到的 swagger.json 就很重要了,正因为它是遵循标准的 OpenAPI 规范生成的,因此只要将这份文档作为输入,传递给 OpenAPI Generator 工具,便可以生成各种语言的 SDK 代码了! 这或许就是制定标准和规范的伟大之处吧,能够让同样的数据在多种不同组织开发的组件间共享,最后形成一种生态。

 

OpenAPI Generator 可以简单理解为就是一个可以基于标准的 OpenAPI 描述文档生成多语言 SDK 的工具,它基于 Swagger Codegen 并且功能方面更为丰富强大。OpenAPI Generator 的安装方式有很多种,官方文档有非常详细的说明。安装好后我们就可以像下面这样从一份 swagger.json 文件导出 SDK 啦~

openapi-generator generate \
    -i swagger.json \  # 输入文件
    -g go \            # 编程语言
    -o sdk/            # 输出目录

OpenAPI Generator 默认生成的 SDK 里包含的一些命名有的地方会比较奇怪,比如用包名+下划线拼接成的文件名,然后文件都是平铺开的没有划分目录,但这些都可以通过修改对应语言的默认模版来定制化,官方文档同样有很多介绍,本文不做赘述。

使用 SDK

有了上面导出的 SDK,我们就可以像下面这样在客户端引入它然后方便地进行调用啦~ 下面是一个 Golang SDK 的示例:

func ListUsers() error {
    // 定义配置
    config := openapi.NewConfiguration()
    config.Host = "my-service.com:9090"
    config.Scheme = "http"
    config.AddDefaultHeader("caller-uid", "admin")
    config.AddDefaultHeader("caller-token", "18a87dfs12sdf9381aww0f2383")
    // 创建客户端对象
    apiClient = openapi.NewAPIClient(config)
    // 调用服务
    bizResponse, httpResponse, err := apiClient.UserApi.UsersGetExecute(openapi.ApiUsersGetRequest{
        Role: "normal-user",
        Status: "Logined",
    })
    if err != nil {
       return err
    }
    if httpResponse == nil || httpResponse.Status != "200 OK" {
       return fmt.Errorf("error occurred")
    }
    // 打印调用结果
    fmt.Println(bizResponse)
}

//bizResponse:
//{
//   Code: 0,
//   Message: "success",
//   Data: {
//      TotalCount: 20,
//      Users: [{
//         Id: 1,
//         Name: "alice",
//         Role: "normal-user",
//         Status: "Logined"
//      }, {
//         Id: 1,
//         Name: "bob",
//         Role: "normal-user",
//         Status: "Logined"
//      }]
//   }
//}

结语

上面便是从服务器端代码到最终多语言的 SDK 的生成路径,总结起来就是下面这样:

  1. 为服务端代码的 HTTP 方法加上 Swagger 注释
  2. 由 swag 工具生成 swagger.json 文档
  3. 将 swagger.json 作为输入参数传递给 openapi-generator 工具生成对应语言的 SDK

可以看出,要想获得一份 OpenAPI SDK,我们不再需要手工编写繁琐的请求发送和解析逻辑,只用按照 Swagger 注释规范填写好接口的签名信息即可~ 这种方式将 SDK 与原始接口相绑定,所见即所得,减少了两侧定义不一致的风险出现,导出 SDK 更加快速高效,又遵循了标准的 OpenAPI 规范。

Leave a Comment.