網站首頁 編程語言 正文
引言
go-doudou從v2版本開始已經支持開發gRPC服務。開發流程跟v1版本是一致的,都是先在svc.go文件里的interface里定義方法,然后執行go-doudou代碼生成命令生成代碼,最后你再寫自定義業務邏輯實現接口。go-doudou的學習曲線非常平滑,對新手極其友好,特別是具有其他編程語言開發背景的開發者,比如從Java、Nodejs或是Python轉過來的。go-doudou非常易學,但是卻能帶來極高的生產力。
本文中我將通過一個小demo來向各位同學展示開發流程是什么樣的,同時也提供了一些最佳實踐。我們將采用go-doudou開發一個通過最大余額法算法解決計算占比不等于100%的問題的gRPC服務,然后通過測試看一下效果。
完整代碼托管在github:github.com/unionj-clou…
準備
安裝go
go-doudou僅支持go 1.16及以上版本。
安裝gRPC編譯器和插件
安裝編譯器protoc
安裝Protobuf編譯器protoc,可參考官方文檔,這里貼一下常見操作系統下的安裝命令:
- Ubuntu系統:
$ apt install -y protobuf-compiler $ protoc --version # 確保安裝v3及以上版本
Mac系統,需要先安裝Homebrew:
$ brew install protobuf $ protoc --version # 確保安裝v3及以上版本
Windows系統,或者Mac系統安裝Homebrew失敗,需從github下載安裝包,解壓后,自行配置環境變量。
安裝插件
- 安裝插件:
$ go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28 $ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2
- 配置環境變量:
$ export PATH="$PATH:$(go env GOPATH)/bin"
最新版本號請跳轉 grpc.io/docs/langua… 查找。
安裝go-doudou
- 如果Go版本低于1.17
go get -v github.com/unionj-cloud/go-doudou/v2@v2.0.3
- 如果Go版本 >= 1.17,推薦采用如下命令全局安裝
go-doudou
命令行工具
go install -v github.com/unionj-cloud/go-doudou/v2@v2.0.3
推薦采用如下命令下載go-doudou作為項目的依賴
go get -v -d github.com/unionj-cloud/go-doudou/v2@v2.0.3
如果遇到410 Gone error
報錯,請先執行如下命令,再執行上述的安裝命令
export GOSUMDB=off
安裝完成后,如果遇到go-doudou: command not found
報錯,請將$HOME/go/bin
配置到~/.bash_profile
文件里,例如:
# .bash_profile # Get the aliases and functions if [ -f ~/.bashrc ]; then . ~/.bashrc fi # User specific environment and startup programs PATH=$PATH:/usr/local/go/bin PATH=$PATH:$HOME/go/bin export PATH
我們可以執行以下命令確認一下安裝是否成功:
? go-doudou -v go-doudou version v2.0.3
初始化項目
做完準備工作我們就可以開始coding了。首先我們需要初始化項目。
go-doudou svc init go-stats -m go-doudou-tutorials/go-stats
go-stats是項目的根路徑,go-doudou會逐層創建文件夾,底層類似執行unix命令mkdir -p
。-m
可以設置模塊名,這里是go-doudou-tutorials/go-stats
go-doudou會生成以下的項目結構。
? go-stats git:(master) ? tree -L 2 . ├── Dockerfile ├── go.mod ├── svc.go └── vo └── vo.go 1 directory, 4 files
svc.go文件里已經聲明了一個接口,用于定義方法,go-doudou會通過這些方法提供的信息來生成proto文件里的rpc代碼。vo包是用來定義go語言結構體的,go-doudou會掃描整個包里聲明的所有結構體來生成proto文件里的message代碼。
定義服務
下面我們來看一下svc.go文件。
/** * Generated by go-doudou v2.0.3. * You can edit it as your need. */ package service import ( "context" "go-doudou-tutorials/go-stats/vo" ) //go:generate go-doudou svc http -c //go:generate go-doudou svc grpc type GoStats interface { // You can define your service methods as your need. Below is an example. // You can also add annotations here like @role(admin) to add meta data to routes for // implementing your own middlewares PageUsers(ctx context.Context, query vo.PageQuery) (data vo.PageRet, err error) }
有兩個//go:generate
指令,12行和13行分別是用來生成RESTful和gRPC服務代碼的,主要是為了方便執行代碼生成命令。如果你用goland的話,你可以像下面的截圖所示那樣操作界面執行命令。
PageUsers是一個示例,我們刪掉它寫上我們的方法。
/** * Generated by go-doudou v2.0.3. * You can edit it as your need. */ package service import ( "context" "go-doudou-tutorials/go-stats/vo" ) //go:generate go-doudou svc http -c //go:generate go-doudou svc grpc // GoStats is a demo gRPC service developed by go-doudou type GoStats interface { // LargestRemainder implements Largest Remainder Method https://en.wikipedia.org/wiki/Largest_remainder_method LargestRemainder(ctx context.Context, payload vo.PercentageReqVo) (data []vo.PercentageRespVo, err error) }
我們還需要在vo包里聲明PercentageReqVo和PercentageRespVo這兩個vo結構體。需要注意的是,GoStats接口的方法里出現的結構體類型參數必須聲明在vo包里,否則go-doudou獲取不到字段信息,就無法在proto文件里生成對應的message。
/** * Generated by go-doudou v2.0.3. * You can edit it as your need. */ package vo //go:generate go-doudou name --file $GOFILE // request vo type PercentageReqVo struct { // key value pairs Data []PercentageVo `json:"data"` // digit number after dot Places int `json:"places"` } // key value pair type PercentageVo struct { // number for something Value int `json:"value"` // unique key for distinguishing something Key string `json:"key"` } // result vo type PercentageRespVo struct { Value int `json:"value"` Key string `json:"key"` Percent float64 `json:"percent"` // formatted percentage PercentFormatted string `json:"percentFormatted"` }
第7行的go-doudou name
命令是go-doudou內置的一個小工具,可以一把生成或更新結構體字段后面的json標簽,默認是首字母小寫的駝峰命名格式。
生成代碼
現在我們可以執行go-doudou svc grpc
命令生成proto文件和gRPC相關的服務端、客戶端代碼了。執行命令后的項目結構如下所示:
? go-stats git:(master) ? tree -L 3 -a . ├── .dockerignore ├── .env ├── .gitignore ├── Dockerfile ├── cmd │ └── main.go ├── config │ └── config.go ├── db │ └── db.go ├── go.mod ├── svc.go ├── svcimpl.go ├── transport │ └── grpc │ ├── annotation.go │ ├── gostats.pb.go │ ├── gostats.proto │ └── gostats_grpc.pb.go └── vo └── vo.go 13 directories, 16 files
go-doudou為我們生成了一些新的文件夾和文件。
.dockerignore
: 用于打包docker鏡像的時候忽略**/*.local
文件
.env
: 配置文件
cmd
: 里面有main.go文件
config
: 用于將環境變量映射到config結構體
db
: 用于建立數據庫連接實例,我們這個demo用不到
svcimpl.go
: 用于編寫自定義的業務邏輯,實現GoStats接口
transport
: proto文件和gRPC相關的服務端、客戶端代碼在這里
在我們實現接口之前,需要執行 go mod tidy
命令來下載依賴。然后我們啟動程序,先看一下是否一切正常。
? go-stats git:(master) ? go run cmd/main.go 2022/11/23 11:07:45 maxprocs: Leaving GOMAXPROCS=16: CPU quota undefined _ _ | | | | __ _ ___ ______ __| | ___ _ _ __| | ___ _ _ / _` | / _ \ |______| / _` | / _ \ | | | | / _` | / _ \ | | | | | (_| || (_) | | (_| || (_) || |_| || (_| || (_) || |_| | __, | ___/ __,_| ___/ __,_| __,_| ___/ __,_| __/ | |___/ 2022-11-23 11:07:45 INF ================ Registered Services ================ 2022-11-23 11:07:45 INF +------------------------------------------+----------------------+ 2022-11-23 11:07:45 INF | SERVICE | RPC | 2022-11-23 11:07:45 INF +------------------------------------------+----------------------+ 2022-11-23 11:07:45 INF | grpc.reflection.v1alpha.ServerReflection | ServerReflectionInfo | 2022-11-23 11:07:45 INF | go_stats.GoStatsService | LargestRemainderRpc | 2022-11-23 11:07:45 INF +------------------------------------------+----------------------+ 2022-11-23 11:07:45 INF =================================================== 2022-11-23 11:07:45 INF Grpc server is listening at [::]:50051 2022-11-23 11:07:45 INF Grpc server started in 1.110168ms
go-doudou依賴了 go.uber.org/automaxprocs
這個包來根據我們給容器做的資源限制來設置處理器P的數量,所以會有第2行的日志輸出。
我們再看一下transport/grpc/gostats.proto文件。
/** * Generated by go-doudou v2.0.3. * Don't edit! * * Version No.: v20221123 */ syntax = "proto3"; package go_stats; option go_package = "go-doudou-tutorials/go-stats/transport/grpc"; message LargestRemainderRpcResponse { repeated PercentageRespVo data = 1 [json_name="data"]; } // request vo message PercentageReqVo { // key value pairs repeated PercentageVo data = 1 [json_name="data"]; // digit number after dot int32 places = 2 [json_name="places"]; } // result vo message PercentageRespVo { int32 value = 1 [json_name="value"]; string key = 2 [json_name="key"]; double percent = 3 [json_name="percent"]; // formatted percentage string percentFormatted = 4 [json_name="percentFormatted"]; } // key value pair message PercentageVo { // number for something int32 value = 1 [json_name="value"]; // unique key for distinguishing something string key = 2 [json_name="key"]; } service GoStatsService { // LargestRemainder implements Largest Remainder Method https://en.wikipedia.org/wiki/Largest_remainder_method rpc LargestRemainderRpc(PercentageReqVo) returns (LargestRemainderRpcResponse); }
如代碼所示,所有message里的字段名稱都是首字母小寫的駝峰命名。其實Protobuf官方給出的規范是下劃線分隔單詞的蛇形命名。因為我們vo包里的結構體的字段的json標簽是首字母小寫的駝峰命名,所以我們這里為了保持一致,就沒有遵循規范。保持一致的作用是我們后面如果需要將protoc生成的結構體轉成vo包里的結構體的時候,可以直接用json序列化反序列化的方式做深拷貝,而無需手工一個個字段去賦值。如果你擔心這個辦法的性能開銷而選擇其他辦法的話,就無所謂了。
go-doudou在生成proto文件的時候會將方法出參中的除error類型之外的其他類型參數都封裝到一個單獨的message中,如這里的LargestRemainderRpcResponse。
go-doudou只支持Protobuf v3。
實現接口
下面我們在svcimpl.go文件中編寫我們的業務代碼。先看一下現在的代碼。
/** * Generated by go-doudou v2.0.3. * You can edit it as your need. */ package service import ( "context" "go-doudou-tutorials/go-stats/config" pb "go-doudou-tutorials/go-stats/transport/grpc" ) var _ pb.GoStatsServiceServer = (*GoStatsImpl)(nil) type GoStatsImpl struct { pb.UnimplementedGoStatsServiceServer conf *config.Config } func (receiver *GoStatsImpl) LargestRemainderRpc(ctx context.Context, request *pb.PercentageReqVo) (*pb.LargestRemainderRpcResponse, error) { //TODO implement me panic("implement me") } func NewGoStats(conf *config.Config) *GoStatsImpl { return &GoStatsImpl{ conf: conf, } }
第13行將*GoStatsImpl類型的nil賦值給pb.GoStatsServiceServer接口類型的_
的作用是確保指針類型的GoStatsImpl結構體實例始終都實現pb.GoStatsServiceServer接口。相當于一種約束。
我們可以根據實際需求往GoStatsImpl結構體里聲明任何字段來存放各種資源,比如外部服務的客戶端,數據庫連接,任意的隊列或者池等等。這里有一個包級別的工廠方法NewGoStats用來注入各種依賴,創建一個指針類型的GoStatsImpl結構體實例。
接下來我們實現LargestRemainderRpc方法。因為go-doudou不支持grpc-gateway和grpc-web等RESTful轉gRPC的轉發器,如果需要在一套程序同時支持RESTful服務和gRPC服務,go-doudou提供的解決方案是分別綁定兩個端口,啟動http服務器和gRPC服務器,復用一套業務邏輯代碼,所以如果考慮后期可能需要加上RESTful支持的話,推薦不要直接實現pb.GoStatsServiceServer接口,而是先實現GoStats接口,然后再調用GoStats接口的實現方法去實現pb.GoStatsServiceServer接口。這樣就實現了業務邏輯代碼的復用。
我個人傾向于不管現在和以后是否需要支持RESTful,都先執行 go-doudou svc http -c
命令生成RESTful相關代碼。
現在的項目結構是這樣的:
? go-stats git:(master) ? tree -L 3 . ├── Dockerfile ├── client │ ├── client.go │ ├── clientproxy.go │ └── iclient.go ├── cmd │ └── main.go ├── config │ └── config.go ├── db │ └── db.go ├── go.mod ├── go.sum ├── gostats_openapi3.go ├── gostats_openapi3.json ├── svc.go ├── svcimpl.go ├── transport │ ├── grpc │ │ ├── annotation.go │ │ ├── gostats.pb.go │ │ ├── gostats.proto │ │ └── gostats_grpc.pb.go │ └── httpsrv │ ├── handler.go │ ├── handlerimpl.go │ └── middleware.go └── vo └── vo.go 8 directories, 21 files
httpsrv包里是http路由以及http請求解析和返回數據序列化相關的代碼。我們可以刪掉也可以先留著。我們看一下svcimpl.go文件有什么變化。
/** * Generated by go-doudou v2.0.3. * You can edit it as your need. */ package service import ( "context" "go-doudou-tutorials/go-stats/config" pb "go-doudou-tutorials/go-stats/transport/grpc" "go-doudou-tutorials/go-stats/vo" "github.com/brianvoe/gofakeit/v6" ) var _ GoStats = (*GoStatsImpl)(nil) var _ pb.GoStatsServiceServer = (*GoStatsImpl)(nil) type GoStatsImpl struct { pb.UnimplementedGoStatsServiceServer conf *config.Config } func (receiver *GoStatsImpl) LargestRemainderRpc(ctx context.Context, request *pb.PercentageReqVo) (*pb.LargestRemainderRpcResponse, error) { //TODO implement me panic("implement me") } func NewGoStats(conf *config.Config) *GoStatsImpl { return &GoStatsImpl{ conf: conf, } } func (receiver *GoStatsImpl) LargestRemainder(ctx context.Context, payload vo.PercentageReqVo) (data []vo.PercentageRespVo, err error) { var _result struct { Data []vo.PercentageRespVo } _ = gofakeit.Struct(&_result) return _result.Data, nil }
第17行和第3642行是新生成的代碼。第17行的作用是確保指針類型的GoStatsImpl始終實現GoStats接口。第3642行是需要我們實現的打樁代碼。
下面我們編寫業務邏輯代碼:
func (receiver *GoStatsImpl) LargestRemainder(ctx context.Context, payload vo.PercentageReqVo) (data []vo.PercentageRespVo, err error) { if len(payload.Data) == 0 { return } input := make([]numberutils.Percentage, 0) for _, item := range payload.Data { input = append(input, numberutils.Percentage{ Value: item.Value, Data: item.Key, }) } numberutils.LargestRemainder(input, int32(payload.Places)) for _, item := range input { data = append(data, vo.PercentageRespVo{ Value: item.Value, Key: item.Data.(string), Percent: item.Percent, PercentFormatted: item.PercentFormatted, }) } return }
go-doudou在 github.com/unionj-cloud/go-doudou/v2/toolkit/numberutils
包里提供了一個工具函數LargestRemainder。此處略過具體的算法實現。
現在我們可以通過復用LargestRemainder方法里的代碼實現LargestRemainderRpc方法。
func (receiver *GoStatsImpl) LargestRemainderRpc(ctx context.Context, request *pb.PercentageReqVo) (*pb.LargestRemainderRpcResponse, error) { var payload vo.PercentageReqVo copier.DeepCopy(request, &payload) data, err := receiver.LargestRemainder(ctx, payload) if err != nil { return nil, err } var ret pb.LargestRemainderRpcResponse err = copier.DeepCopy(data, &ret.Data) if err != nil { return nil, err } return &ret, nil }
我們無需手工編寫pb.PercentageReqVo轉vo.PercentageReqVo和[]vo.PercentageRespVo轉[]*pb.PercentageRespVo的代碼,直接用 github.com/unionj-cloud/go-doudou/v2/toolkit/copier
包里的DeepCopy函數做深拷貝即可。
測試服務
因為我們生成了新的代碼,導入了新的依賴,所以我們需要再執行一下 go mod tidy
。然后我們啟動服務,測試一下效果。
? go-stats git:(master) ? go mod tidy && go run cmd/main.go 2022/11/23 13:18:13 maxprocs: Leaving GOMAXPROCS=16: CPU quota undefined _ _ | | | | __ _ ___ ______ __| | ___ _ _ __| | ___ _ _ / _` | / _ \ |______| / _` | / _ \ | | | | / _` | / _ \ | | | | | (_| || (_) | | (_| || (_) || |_| || (_| || (_) || |_| | __, | ___/ __,_| ___/ __,_| __,_| ___/ __,_| __/ | |___/ 2022-11-23 13:18:13 INF ================ Registered Services ================ 2022-11-23 13:18:13 INF +------------------------------------------+----------------------+ 2022-11-23 13:18:13 INF | SERVICE | RPC | 2022-11-23 13:18:13 INF +------------------------------------------+----------------------+ 2022-11-23 13:18:13 INF | go_stats.GoStatsService | LargestRemainderRpc | 2022-11-23 13:18:13 INF | grpc.reflection.v1alpha.ServerReflection | ServerReflectionInfo | 2022-11-23 13:18:13 INF +------------------------------------------+----------------------+ 2022-11-23 13:18:13 INF =================================================== 2022-11-23 13:18:13 INF Grpc server is listening at [::]:50051 2022-11-23 13:18:13 INF Grpc server started in 1.001238ms
我個人傾向于采用 evans 去幫助測試和調試gRPC服務。
? go-stats git:(master) ? evans -r repl -p 50051 ______ | ____| | |__ __ __ __ _ _ __ ___ | __| \ \ / / / _. | | '_ \ / __| | |____ \ V / | (_| | | | | | __ \ |______| _/ __,_| |_| |_| |___/ more expressive universal gRPC client go_stats.GoStatsService@127.0.0.1:50051> show service +----------------+---------------------+-----------------+-----------------------------+ | SERVICE | RPC | REQUEST TYPE | RESPONSE TYPE | +----------------+---------------------+-----------------+-----------------------------+ | GoStatsService | LargestRemainderRpc | PercentageReqVo | LargestRemainderRpcResponse | +----------------+---------------------+-----------------+-----------------------------+ go_stats.GoStatsService@127.0.0.1:50051> service GoStatsService go_stats.GoStatsService@127.0.0.1:50051> call LargestRemainderRpc <repeated> data::value (TYPE_INT32) => 20 <repeated> data::key (TYPE_STRING) => apple <repeated> data::value (TYPE_INT32) => 30 <repeated> data::key (TYPE_STRING) => banana <repeated> data::value (TYPE_INT32) => 40 <repeated> data::key (TYPE_STRING) => pear <repeated> data::value (TYPE_INT32) => places (TYPE_INT32) => 2 { "data": [ { "key": "apple", "percent": 22.22, "percentFormatted": "22.22%", "value": 20 }, { "key": "banana", "percent": 33.33, "percentFormatted": "33.33%", "value": 30 }, { "key": "pear", "percent": 44.45, "percentFormatted": "44.45%", "value": 40 } ] }
我們輸入apple 20kg,banana 30kg,pear 40kg和保留2位小數,然后得到了我們期望的結果:22.22 + 33.33 + 44.45 = 100。
總結
原文鏈接:https://juejin.cn/post/7173833879927652388
相關推薦
- 2022-11-18 Shell實現批量操作文件的方法詳解_linux shell
- 2022-12-07 C++?IO設備讀寫功能實現詳解_C 語言
- 2023-07-10 Python使用MongoDB數據庫
- 2021-11-08 深入解析golang中的標準庫flag_Golang
- 2022-07-12 Samba安裝與配置流程
- 2022-06-26 Python使用Tkinter?GUI實現輸入驗證功能_python
- 2022-09-08 pytest實現多進程與多線程運行超好用的插件_python
- 2022-11-12 NetCore?配置Swagger的詳細代碼_實用技巧
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支