天天看點

proto管理工具buf體驗

作者:go算法架構leetcode

buf之于proto,類似go mod之于golang,它通過buf.yaml來聲明一個proto的module,作為管理的最小單元,友善其它proto庫引用,也可以用來聲明對其它庫的依賴,包括從遠端倉庫BSR(全稱 Buf Schema Registry)拉取依賴的proto庫。它同時提供了代碼生成管理工具buf.gen.yaml友善我們指定protoc插件,以及對這些protoc插件的安裝和管理,我們不用本地配置protoc工具和各種protoc插件,大大提升了開發效率。

buf是使用golang編寫的,位址位于https://github.com/bufbuild/buf,是以安裝方式除了使用brew,也可以使用go install

brew install bufbuild/buf/buf
go install github.com/bufbuild/buf/cmd/[email protected]           

安裝完成後檢查下版本

buf --version
1.15.1           

官方的BSR位址是https://buf.build/,我們可以https://buf.build/login頁面進行注冊登入。使用體驗類似github

proto管理工具buf體驗

buf的使用可以參考官方文檔https://docs.buf.build/tutorials/getting-started-with-buf-cli 和https://docs.buf.build/tutorials/getting-started-with-bsr,介紹得非常詳盡,buf的核心功能如下,就不一一翻譯了:

The ability to manage Protobuf assets on the Buf Schema Registry (BSR).
A linter that enforces good API design choices and structure.
A breaking change detector that enforces compatibility at the source code or wire level.
A generator that invokes your plugins based on configurable templates.
A formatter that formats your Protobuf files in accordance with industry standards.
Integration with the Buf Schema Registry, including full dependency management.           

下面我們就follow官方的文檔來學習下buff的使用,首先下載下傳官方的例子

git clone https://github.com/bufbuild/buf-tour
cd buf-tour/start/getting-started-with-buf-cli           

然後初始化一個module

cd proto
buf mod init           

可以看到,新生成了一個buf.yaml檔案,也就是module的聲明檔案

version: v1
breaking:
  use:
    - FILE
lint:
  use:
    - DEFAULT           

我們可以通過buf build指令檢查目前module聲明的合法性

buf build 
echo$?
0           

buf.yaml 所在的位置代表了一個module的根目錄,一個module是一系列關系密切的proto的集合。

緊接着我們看下如何使用buf.gen.yaml來管理目前項目依賴的protoc插件來生成目标代碼。

cd ..
touch buf.gen.yaml           

比如我們希望依賴protoc-gen-go(https://github.com/protocolbuffers/protobuf-go)插件來生成golang代碼,以及connect-go(https://github.com/bufbuild/connect-go)插件來生成grpc,或者http server代碼,我們可以這樣配置buf.gen.yaml

version: v1
managed:
  enabled: true
  go_package_prefix:
    default: github.com/bufbuild/buf-tour/gen
plugins:
  - plugin: buf.build/protocolbuffers/go
    out: gen
    opt: paths=source_relative
  - plugin: buf.build/bufbuild/connect-go
    out: gen
    opt: paths=source_relative           

配置完成後,我們就不用關心如何下載下傳,部署和配置插件了,直接運作指令

buf generate proto           

就可以完成上述操作,并生成對應的golang代碼。

% tree
.
|____proto
| |____buf.yaml
| |____google
| | |____type
| | | |____datetime.proto
| |____pet
| | |____v1
| | | |____pet.proto
|____gen
| |____google
| | |____type
| | | |____datetime.pb.go
| |____pet
| | |____v1
| | | |____pet.pb.go
| | | |____petv1connect
| | | | |____pet.connect.go
|____buf.gen.yaml           

其中gen目錄下的代碼就是我們新生成的。buf還有一些相關的管理工具,比如lint

buf lint proto
proto/google/type/datetime.proto:17:1:Package name "google.type" should be suffixed with a correctly formed version, such as "google.type.v1".
proto/pet/v1/pet.proto:42:10:Field name "petID" should be lower_snake_case, such as "pet_id".
proto/pet/v1/pet.proto:47:9:Service name "PetStore" should be suffixed with "Service".           

可以看到,我們聲明的proto有三個不規範的地方:1,引用的包沒有加版本号 2,字段名不是下劃線格式 3,服務不是以Service結尾,我們修改下

syntax = "proto3";


package pet.v1;


...


message DeletePetRequest {
-  string petID = 1;
+  string pet_id = 1;
}


message DeletePetResponse {}


-service PetStore {
+service PetStoreService {
  rpc GetPet(GetPetRequest) returns (GetPetResponse) {}
  rpc PutPet(PutPetRequest) returns (PutPetResponse) {}
  rpc DeletePet(DeletePetRequest) returns (DeletePetResponse) {}
}           

但是,引用的包google.type是官方的包,我們改不了,我們可以在buf.yaml裡将它忽略掉

version: v1
 breaking:
   use:
     - FILE
 lint:
   use:
     - DEFAULT
+  ignore:
+    - google/type/datetime.proto           

重新運作lint指令,我們發現已經通過了,當然,對于現存的proto檔案,即使不符合規範,我們也改不了,我們可以通過指令來生成忽略設定配置

% buf lint proto --error-format=config-ignore-yaml
version: v1
lint:
  ignore_only:
    FIELD_LOWER_SNAKE_CASE:
      - pet/v1/pet.proto
    SERVICE_SUFFIX:
      - pet/v1/pet.proto           

下一次,我們就可以依據已經生成的忽略模闆檔案來忽略掉存量不規範的格式

buf lint --error-format=config-ignore-yaml           

如果我們的proto 變更發生了老版本不相容的問題,我們也可以通過指令來檢查

% buf breaking proto --against "../../.git#subdir=start/getting-started-with-buf-cli/proto"           

比如我們發生了這樣的變更

message Pet {
-  PetType pet_type = 1;
+  string pet_type = 1;
  string pet_id = 2;
  string name = 3;
}           

通過檢查不相容變更就可以得到

% buf breaking proto --against "../../.git#subdir=start/getting-started-with-buf-cli/proto"
proto/pet/v1/pet.proto:33:3:Field "1" on message "PutPetRequest" changed type from "enum" to "string".           

生成golang代碼後 buf還提供了curl工具來進行測試。

go mod init github.com/bufbuild/buf-tour
go: creating new go.mod: module github.com/bufbuild/buf-tour
go: to add module requirements and sums:
        go mod tidy           
mkdir server
touch server/main.go
go mod tidy           

其中main.go檔案如下

package main


import (
    "context"
    "fmt"
    "log"
    "net/http"
    petv1 "github.com/bufbuild/buf-tour/gen/pet/v1"
  petv1connect  "github.com/bufbuild/buf-tour/gen/pet/v1/petv1connect"
    "github.com/bufbuild/connect-go"
    "golang.org/x/net/http2"
    "golang.org/x/net/http2/h2c"
)


const address = "localhost:8080"


func main() {
    mux := http.NewServeMux()
    path, handler := petv1connect.NewPetStoreServiceHandler(&petStoreServiceServer{})
    mux.Handle(path, handler)
    fmt.Println("... Listening on", address)
    http.ListenAndServe(
        address,
        // Use h2c so we can serve HTTP/2 without TLS.
        h2c.NewHandler(mux, &http2.Server{}),
    )
}


// petStoreServiceServer implements the PetStoreService API.
type petStoreServiceServer struct {
    petv1connect.UnimplementedPetStoreServiceHandler
}


// PutPet adds the pet associated with the given request into the PetStore.
func (s *petStoreServiceServer) PutPet(
    ctx context.Context,
    req *connect.Request[petv1.PutPetRequest],
) (*connect.Response[petv1.PutPetResponse], error) {
    name := req.Msg.GetName()
    petType := req.Msg.GetPetType()
    log.Printf("Got a request to create a %v named %s", petType, name)
    return connect.NewResponse(&petv1.PutPetResponse{}), nil
}           

啟動服務

go run ./server   
... Listening on localhost:8080           

然後我們就可以發起請求了

buf curl \
--schema proto/pet/v1/pet.proto \
--data '{"pet_type": "PET_TYPE_SNAKE", "name": "Ekans"}' \
http://localhost:8080/pet.v1.PetStoreService/PutPet


{}           

接着,我們體驗下使用buf schema registry

cd buf-tour/start/getting-started-with-bsr           

注冊bsr賬号,建立repository,然後把buf.yaml裡的包名聲明成如下格式

version: v1
  + name: buf.build/<USER>/petapis
    breaking:
      use:
        - FILE
    lint:
      use:
        - DEFAULT           

改動完畢後,我們可以加buf.md說明檔案,然後運作

buf push           

推送到遠端。當然,我們也可以通過deps聲明依賴的遠端proto檔案

version: v1
 name: buf.build/<USER>/petapis
 +deps:
 +  - buf.build/googleapis/googleapis
 breaking:
   use:
     - FILE
 lint:
   use:
     - DEFAULT           

然後通過buf mod update指令來下載下傳依賴,并且生成lock檔案buf.lock

buf mod update           

生成的lock檔案内容如下

# Generated by buf. DO NOT EDIT.
version: v1
deps:
  - remote: buf.build
    owner: googleapis
    repository: googleapis
    commit: 62f35d8aed1149c291d606d958a7ce32           

預設情況下,會将所有的proto檔案生成對應的go代碼,但是對于googleapis/googleapis這樣的官方proto檔案,github上會有官方維護的package,我應該使用官方的包,而不是自己重新生成一遍,我們可以在buf.gen.yaml裡面添加except指令來進行排除,

+    except:
 +      - buf.build/googleapis/googleapis           

完整的變動如下,這樣我們就可以直接使用官方生成的go package

version: v1
 managed:
   enabled: true
   go_package_prefix:
     default: github.com/bufbuild/buf-tour/petstore/gen
 +    except:
 +      - buf.build/googleapis/googleapis
 plugins:
 -  - plugin: buf.build/protocolbuffers/go
 -    out: gen
 -    opt: paths=source_relative
   - plugin: buf.build/bufbuild/connect-go
     out: gen
     opt: paths=source_relative           

相應的,本地就不會生成官方的proto對應的golang代碼

gen
└── pet
    └── v1
        ├── pet.pb.go
        └── petv1connect
            └── pet.connect.go           

至此,buf的核心功能介紹完畢,感興趣的同學可以去學習下官網的wiki,非常詳盡。

繼續閱讀