<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          字節(jié)都在用的代碼自動生成

          共 3114字,需瀏覽 7分鐘

           ·

          2023-06-07 12:10

          背景

          如果有一份接口定義,前端和后端都能基于此生成相應(yīng)端的代碼,不僅能降低前后端溝通成本,而且還能提升研發(fā)效率。

          字節(jié)內(nèi)部的 RPC 定義主要基于 thrift 實現(xiàn),thrift 定義了數(shù)據(jù)結(jié)構(gòu)和函數(shù),那么是否可以用來作為接口定義提供給前端使用呢?如果可以作為接口定義,是不是也可以通過接口定義自動生成請求接口的代碼呢?答案是肯定的,字節(jié)內(nèi)部已經(jīng)衍生出了多個基于 thrift 的代碼生成工具,本篇文章主要介紹如何通過 thrift 生成前端接口調(diào)用的代碼。

          接口定義

          接口定義,顧名思義就是用來定義接口的語言,由于字節(jié)內(nèi)部廣泛使用的 thrift 基本上滿足接口定義的要求,所以我們不妨直接把 thrift 當成接口定義。

          thrift 是一種跨語言的遠程過程調(diào)用 (RPC) 框架,如果你對 Typescript 比較熟悉的話,那它的結(jié)構(gòu)看起來應(yīng)該很簡單,看個例子:

                
                namespace?go?namesapce
          //?請求的結(jié)構(gòu)體
          struct?GetRandomRequest?{
          ?1:?optional?i32?min,
          ?2:?optional?i32?max,
          ?3:?optional?string?extra
          }
          //?響應(yīng)的結(jié)構(gòu)體
          struct?GetRandomResponse?{
          ?1:?optional?i64?random_num
          }
          //?定義服務(wù)
          service?RandomService?{
          ?GetRandomResponse?GetRandom?(1:?GetRandomRequest?req)
          }

          示例中的 service 可以看成是一組函數(shù),每個函數(shù)可以看成是一個接口。我們都知道,對于 restful 接口,還需要定義接口路徑(比如 /getUserInfo)和參數(shù)(query 參數(shù)、body 參數(shù)等),我們可以通過 thrift 注解來表示這些附加信息。

                
                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.source 用來指定參數(shù)的位置,query 表示是 query 參數(shù),body 表示 body 參數(shù);api.get="/api/get-random" 表示接口路徑是 /api/get-random,請求方法是 GET;

          生成 Typescript

          上面我們已經(jīng)有了接口定義,那么對應(yīng)的 Typescript 應(yīng)該就呼之欲出了,一起來看代碼:

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

          生成 Typescript 后,我們無需關(guān)心生成的代碼長什么樣,直接調(diào)用 GetRandom 即可。

          架構(gòu)設(shè)計

          要實現(xiàn)基于 thrift 生成代碼,最核心的架構(gòu)如下:

          c6f88dc1fdb9c70c7f2de3655ffaafaf.webp因為 thrift 的內(nèi)容我們不能直接拿來用,需要轉(zhuǎn)化成中間代碼(IR),這里的中間代碼通常是 json、AST 或者自定義的 DSL。如果中間代碼是 json,可能的結(jié)構(gòu)如下:

                
                {
          ??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:?[],
          ??},
          };

          為了保持架構(gòu)的開放性,我們在核心鏈路上插入了 PrePlugin 和 PostPlugin,其中 PrePlugin 決定了 thrift 如何轉(zhuǎn)化成 IR,PostPlugin 決定 IR 如何生成目標代碼。

          這里之所以是「目標代碼」而不是「Typescript 代碼」,是因為我希望不同的 PostPlugin 可以產(chǎn)生不同的目標代碼,比如可以通過 TSPostPlugin 生成 Typescript 代碼,通過 GoPostPlugin 生成 go 語言的代碼。

          總結(jié)

          代碼生成這塊的內(nèi)容還有很多可以探索的地方,比如如何解析 thrift?是找第三方功能生成 AST 還是通過 pegjs 解析成自定義的 DSL?多文件聯(lián)編如何處理、字段名 case 如何轉(zhuǎn)換、運行時類型校驗、生成的代碼如何與 useRequest 或 ReactQuery 集成等。

          thrift 其實可以看成接口定義的具體實現(xiàn),如果 thrift 不滿足你的業(yè)務(wù)場景,也可以自己實現(xiàn)一套類似的接口定義語言;接口定義作為前后端的約定,可以降低前后端的溝通成本;代碼生成,可以提升前端代碼的質(zhì)量和研發(fā)效率。

          如果本文對你有啟發(fā),歡迎點贊、關(guān)注、留言交流。

          作者:探險家火焱https://juejin.cn/post/7220054775298359351
          瀏覽 73
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产91乱码一区二区三区 | 久久成人理论电影手机 | 亚洲精品永久久久久久 | 亚洲视频在线免费观 | 午夜香蕉网 |