The code that is used by the bytes is automatically generated

background

If there is an interface definition, both the front-end and the back-end can generate corresponding codes based on this, which can not only reduce the communication cost between the front-end and the back-end, but also improve the efficiency of research and development.

The RPC definition inside Byte is mainly based on thrift implementation. Thrift defines data structures and functions, so can it be used as an interface definition for front-end use? If it can be used as an interface definition, can the code for requesting the interface be automatically generated through the interface definition ? The answer is yes, multiple thrift-based code generation tools have been derived within Byte. This article mainly introduces how to generate code for front-end interface calls through thrift.

Interface definition

Interface definition, as the name suggests, is a language used to define interfaces. Since thrift, which is widely used in bytes, basically meets the requirements of interface definitions, we might as well regard thrift as interface definition directly.

Thrift is a cross-language remote procedure call (RPC) framework. If you are familiar with Typescript, its structure should look very simple. Take an example:

namespace go namesapce

// 请求的结构体
struct GetRandomRequest {
    1: optional i32 min,
    2: optional i32 max,
    3: optional string extra
}

// 响应的结构体
struct GetRandomResponse {
    1: optional i64 random_num
}

// 定义服务
service RandomService {
   GetRandomResponse GetRandom (1: GetRandomRequest req)
}
复制代码

The service in the example can be regarded as a group of functions, and each function can be regarded as an interface. We all know that for restful interfaces, it is also necessary to define interface paths (such as /getUserInfo) and parameters (query parameters, body parameters, etc.), and we can express these additional information through thrift annotations .

namespace go namesapce

struct GetRandomRequest {
    1: optional i32 min (api.source = "query"),
    2: optional i32 max (api.source = "query"),
    3: optional string extra (api.source = "body"),
}

struct GetRandomResponse {
    1: optional i64 random_num,
}

// Service
service RandomService {
   GetRandomResponse GetRandom (1: GetRandomRequest req) (api.get = "/api/get-random"),
}
复制代码

api.sourceIt is used to specify the position of the parameter, queryindicating that it is a query parameter, bodyindicating a body parameter; api.get="/api/get-random"indicating that the interface path is /api/get-random, and the request method is GET;

Generate Typescript

We already have the interface definition above, so the corresponding Typescript should be ready to come out, let's look at the code together:

interface GetRandomRequest {
    min: number;
    max: number;
    extra: string;
}

interface GetRandomResponse {
    random_num: number;
}

async function GetRandom(req: GetRandomRequest): Promise<GetRandomResponse> {
    return request<GetRandomResponse>({
        url: '/api/get-random',
        method: 'GET',
        query: {
            min: req.min,
            max: req.max,
        },
        body: {
            extra: req.extra,
        }
    });
}

复制代码

After generating Typescript, we don't need to care about what the generated code looks like, GetRandomjust .

architecture design

要实现基于 thrift 生成代码,最核心的架构如下:

image.png 因为 thrift 的内容我们不能直接拿来用,需要转化成中间代码(IR),这里的中间代码通常是 json、AST 或者自定义的 DSL。如果中间代码是 json,可能的结构如下:

{
    name: 'GetRandom',
    method: 'get',
    path: '/api/get-random',
    req_schema: {
        query_params: [
            {
                name: 'min',
                type: 'int',
                optional: true,
            },
            {
                name: 'max',
                type: 'int',
                optional: true,
            }
        ],
        body_params: [
            {
                name: 'extra',
                type: 'string',
                optional: true,
            }
        ],
        header_params: [],
    },
    resp_schema: {
        header_params: [],
        body_params: [],
    } 
}
复制代码

为了保持架构的开放性,我们在核心链路上插入了 PrePlugin 和 PostPlugin,其中 PrePlugin 决定了 thrift 如何转化成 IR,PostPlugin 决定 IR 如何生成目标代码。

这里之所以是「目标代码」而不是「Typescript 代码」,是因为我希望不同的 PostPlugin 可以产生不同的目标代码,比如可以通过 TSPostPlugin 生成 Typescript 代码,通过 GoPostPlugin 生成 go 语言的代码。

总结

代码生成这块的内容还有很多可以探索的地方,比如如何解析 thrift?是找第三方功能生成 AST 还是通过 pegjs 解析成自定义的 DSL?多文件联编如何处理、字段名 case 如何转换、运行时类型校验、生成的代码如何与 useRequest 或 ReactQuery 集成等。

thrift 其实可以看成接口定义的具体实现,如果 thrift 不满足你的业务场景,也可以自己实现一套类似的接口定义语言;接口定义作为前后端的约定,可以降低前后端的沟通成本;代码生成,可以提升前端代码的质量和研发效率。

如果本文对你有启发,欢迎点赞、关注、留言交流。

Guess you like

Origin juejin.im/post/7220054775298359351