用代码生成简化 Go 的后端逻辑

用代码生成简化 Go 的后端逻辑

后端开发免不了大量重复的样板代码,尤其围绕在数据库查询和 API 服务器上。用手写这些代码既慢又容易出错。代码生成工具(codegen)能把声明式的规格转成型别安全的 Go 代码,你也不用花心力维护。

这篇文章介绍两个工具。

为什么要用代码生成

用 sqlc 生成 SQL 代码

sqlc 是什么

sqlc 读取 .sql 文件,生成带类型参数和结果的 Go 函数来执行那些查询。

sqlc 怎么用

  1. 写好书据结构(数据表结构)和查询。
  2. 在 sqlc.yaml 里设置输入与输出。
  3. 执行 sqlc generate。

示例

假设有一个 users 数据表。

-- schema.sql
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name TEXT NOT NULL,
    email TEXT UNIQUE NOT NULL
);

用 email 找出用户的查询如下。

-- queries.sql
-- name: GetUserByEmail :one
SELECT id, name, email FROM users WHERE email = $1;

sqlc.yaml 的配置。

version: "1"
packages:
  - name: "db"
    path: "./db"
    queries: "./queries.sql"
    schema: "./schema.sql"
    engine: "postgresql"

执行:

sqlc generate

会生成像这样的方法。

func (q *Queries) GetUserByEmail(ctx context.Context, email string) (User, error)

在后端这样使用。

user, err := dbQueries.GetUserByEmail(ctx, "alice@example.com")
if err != nil {
    // handle error
}
fmt.Println("User:", user.Name)

手动处理 SQL 字符串和扫描数据行的工作不见了。代码变得更干净、更安全。

用 oapi-codegen 生成服务器代码

oapi-codegen 是什么

oapi-codegen 从 OpenAPI 规格生成 Go 的服务器和客户端代码。它会创建接口和请求、响应的类型。剩下的就是实现业务逻辑。

oapi-codegen 怎么用

  1. 用 OpenAPI 的 YAML 或 JSON 文件定义 API。
  2. 执行 oapi-codegen。
  3. 实现生成的接口方法。

示例

准备一份小的 OpenAPI 规格 api.yaml。

openapi: 3.0.0
info:
  title: User API
  version: 1.0.0
paths:
  /users/{email}:
    get:
      summary: Get user by email
      parameters:
        - name: email
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: User found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/User"
        "404":
          description: User not found
components:
  schemas:
    User:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        email:
          type: string

生成服务器代码。

oapi-codegen -generate types,server -package api -o api.gen.go api.yaml

这会生成请求、响应的类型(User struct),以及带有方法的 ServerInterface。

GetUsersEmail(ctx context.Context, email string) (api.User, error)

实现接口。

type ServerImpl struct {
    db *db.Queries
}
 
func (s *ServerImpl) GetUsersEmail(ctx context.Context, email string) (api.User, error) {
    user, err := s.db.GetUserByEmail(ctx, email)
    if err != nil {
        return api.User{}, err
    }
    return api.User{
        Id:    int64(user.ID),
        Name:  user.Name,
        Email: user.Email,
    }, nil
}

接上服务器。

router := api.NewRouter(&ServerImpl{db: dbQueries})
http.ListenAndServe(":8080", router)

同时使用 sqlc 和 oapi-codegen 的好处

结论

同时使用 sqlc 和 oapi-codegen,就能在 Go 后端自动化数据库访问和 API 服务器代码。要写的样板变少了,运行时的错误也变少了。该专注的地方是应用的核心逻辑。

© Melvin Laplanche - All rights reserved.