go-zero を学ぶ
go-zero なるフレームワークがあるとのことなので使ってみる
go-zero を操作するために goctl
というコマンドラインが提供されている。
goctl
を使うと作成した api ファイルを元に Go, TypeScript, JavaScript, Dart, Kotlin, Android, iOS のコードを生成してくれる
go-zero の背景
2018年の初めに、私たちはシステムをモノリシックなJava+MongoDBのアーキテクチャからマイクロサービスアーキテクチャに再設計することを決定しました。調査と比較の結果、次のような選択をしました:
- Golangベース
- 優れたパフォーマンス
- シンプルな構文
- 証明されたエンジニアリングの効率性
- 極めてスムーズなデプロイ経験
- サーバーリソースの消費量が少ない
- 独自設計のマイクロサービスアーキテクチャ
- マイクロサービスアーキテクチャの設計に豊富な経験があります
- 問題の特定が容易
- 機能の拡張が容易
go-zeroの設計における考慮事項
マイクロサービスアーキテクチャの設計により、安定性と生産性の確保を期待しています。そして、最初から以下の設計原則を持っています:
- シンプルに保つこと
- 高い可用性
- 高い並行性での安定性
- 拡張性が高いこと
- レジリエンスの設計、障害中心のプログラミング
- ビジネスロジックの開発に対して使いやすくすること、複雑さをカプセル化すること
- 一つのことを一つの方法で行う
半年近くの時間をかけて、モノリシックなシステムからマイクロサービスシステムへの移行を完了し、2018年8月にデプロイしました。新しいシステムは、ビジネスの成長とシステムの安定性を保証しました。
go-zeroの実装と機能
go-zeroは、多くのエンジニアリングプラクティスを統合したWebおよびRPCフレームワークです。主な機能は以下の通りです:
- パワフルなツールが組み込まれており、少ないコードで済む
- シンプルなインターフェース
- net/httpと完全に互換性がある
- ミドルウェアがサポートされており、拡張が容易
- 高いパフォーマンス
- 障害中心のプログラミング、レジリエンスの設計
- 組み込みのサービスディスカバリ、負荷分散
- 組み込みの同時実行制御、適応型サーキットブレーカー、適応型負荷分散、自動トリガー、自動リカバリー
- APIリクエストパラメータの自動検証
- チェーン化されたタイムアウト制御
- データキャッシュの自動管理
- コールトレーシング、メトリクス、モニタリング
- 高い並行性の保護
READMEを読む限り良さそうなフレームワークだ
ドキュメント に沿って、やってみるか。
とりあえず、Microservices で作るときの使い勝手知りたい。
想定は、ゴリゴリに Microservices でシステム作ってるチームではなく、Microservices はわかったぞ。作ってみるか!ってチームを想定している。
環境構築
大まかな流れ
- Go をインストール
- ドキュメントでは、1.15.1 使ってたけど、1.20.1 (2023-05-31時点) を使って進める
- Homebrew 使っているのであれば、
brew install golang
でもOK
-
Go のモジュール設定
- すっ飛ばしても良さそう
-
Goctl のインストール
-
brew install goctl
でもインストールできた
-
-
protoc と protoc-gen-go をインストール
-
goctl env check -i -f -v
で必要なものインストールされているか確認できる - Homebrew 使っているのであれば、
brew install protobuf protoc-gen-go protoc-gen-go-grpc
でインストール可能
-
開発するに当たり、ドキュメントが充実しているのでよく読む
今回は割愛する
とりあえず、Getting Started でサービス作ってみる
お題は、「簡単な注文サービス」
モジュールは、「注文管理」と「ユーザー管理」の2つ。
管理と書いたけど、どちらも取得するだけの簡単なもの
概要知るのに重要なので翻訳
サービスの設計分析
シナリオの要約によると、注文は直接ユーザーに関連しており、HTTPプロトコルを介してデータにアクセスします。また、注文内部ではユーザーに関するいくつかの基本的なデータを取得する必要があります。マイクロサービスアーキテクチャを使用しているため、2つのサービス(user, order)はデータをやり取りする必要があります。つまり、サービス間の通信です。ここまで来たところで、適切な通信プロトコルを使用してサービス間の通信を実現する必要があります。ここでは、サービス間の通信にRPCを選択し、すでに「RPCサービスの役割は何ですか?」というシナリオをより良く説明しました。もちろん、開発前の設計分析だけではサービスに関してはまだ多くの要素がありますので、ここでは詳細には触れません。以上から、私たちは次の2つのサービスを初期のデモ実装に使用する必要があることがわかります。
- ユーザーRPC
- 注文API
まずは、プロジェクト作る
お決まりをただただ書く
cd $GOPATH/src/github.com/135yshr
mkdir go-zero-example
cd go-zero-example
go mod init go-zero-example
proto ファイル作る
mkdir -p mall/user/rpc
vim mall/user/rpc/user.proto
syntax = "proto3";
package user;
// protoc-gen-go version is greater than 1.4.0, proto file needs to add go_package, otherwise it can't be generated
option go_package = "./user";
message IdRequest {
string id = 1;
}
message UserResponse {
// user id
string id = 1;
// user name
string name = 2;
// user gender
string gender = 3;
}
service User {
rpc getUser(IdRequest) returns(UserResponse);
}
proto ファイル元にコードジェネレート!
cd mall/user/rpc
goctl rpc protoc user.proto --go_out=./types --go-grpc_out=./types --zrpc_out=.
go-zero 知る前は、 go gen
で生成してた。
都度、そのディレクトリに移動しないとだめなんだろうか。。。
ちょっと面倒だ
ビジネスロジックを書き換え
@@ -24,7 +24,8 @@ func NewGetUserLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetUserLo
}
func (l *GetUserLogic) GetUser(in *user.IdRequest) (*user.UserResponse, error) {
- // todo: add your logic here and delete this line
-
- return &user.UserResponse{}, nil
+ return &user.UserResponse{
+ Id: "1",
+ Name: "test",
+ }, nil
}
コピペして気付かなかったけど、ID
を文字列で作ってたのか。
とりあえずいいか。
api ファイルを作ってコードを生成する
mkdir -p order/api && cd order/api
vim order.api
cd -
type(
OrderReq {
Id string `path:"id"`
}
OrderReply {
Id string `json:"id"`
Name string `json:"name"`
}
)
service order {
@handler getOrder
get /api/order/get/:id (OrderReq) returns (OrderReply)
}
APIファイルを元にコードをジェネレート!
goctl api go -api order.api -dir .
ディレクトリを都度移動しないとだめなんだろうか。。。(2回目
ユーザー情報を取得するRPCを呼び出しできるように設定する
@@ -1,6 +1,9 @@
package config
-import "github.com/zeromicro/go-zero/rest"
+import (
+ "github.com/zeromicro/go-zero/rest"
+ "github.com/zeromicro/go-zero/zrpc"
+)
@@ -4,4 +4,5 @@ import "github.com/zeromicro/go-zero/rest"
type Config struct {
rest.RestConf
+ UserRpc zrpc.RpcClientConf
}
@@ -1,3 +1,8 @@
Name: order
Host: 0.0.0.0
Port: 8888
+UserRpc:
+ Etcd:
+ Hosts:
+ - 127.0.0.1:2379
+ Key: user.rpc
注文管理のビジネスロジックを書く
@@ -1,15 +1,19 @@
package svc
import (
+ "github.com/SwipeVideo/go-zero-example/mall/user/rpc/types/user"
"github.com/SwipeVideo/go-zero-example/order/api/internal/config"
+ "github.com/zeromicro/go-zero/zrpc"
)
type ServiceContext struct {
- Config config.Config
+ Config config.Config
+ UserRpc user.UserClient
}
func NewServiceContext(c config.Config) *ServiceContext {
return &ServiceContext{
- Config: c,
+ Config: c,
+ UserRpc: user.NewUserClient(zrpc.MustNewClient(c.UserRpc).Conn()),
}
}
@@ -2,9 +2,12 @@ package logic
import (
"context"
+ "errors"
+ "github.com/SwipeVideo/go-zero-example/mall/user/rpc/types/user"
"github.com/SwipeVideo/go-zero-example/order/api/internal/svc"
"github.com/SwipeVideo/go-zero-example/order/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
@@ -23,8 +26,20 @@ func NewGetOrderLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetOrder
}
}
-func (l *GetOrderLogic) GetOrder(req *types.OrderReq) (resp *types.OrderReply, err error) {
- // todo: add your logic here and delete this line
+func (l *GetOrderLogic) GetOrder(req *types.OrderReq) (*types.OrderReply, error) {
+ user, err := l.svcCtx.UserRpc.GetUser(l.ctx, &user.IdRequest{
+ Id: "1",
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ if user.Name != "test" {
+ return nil, errors.New("not found")
+ }
- return
+ return &types.OrderReply{
+ Id: req.Id,
+ Name: "test order",
+ }, nil
}
いろいろなライブラリのダウンロード!
go mod tidy
作成したプログラムを起動する
大まかな手順
- etcd を起動する
- user モジュールを起動する
- order モジュールを起動する
Getting Started では、etcd
コマンドで実行しているが、今回は Docker で etcd を起動する
docker network create app-tier --driver bridge
docker run -d --rm \
--name Etcd-server \
--network app-tier \
--publish 12379:2379 \
--publish 12380:2380 \
--env ALLOW_NONE_AUTHENTICATION=yes \
--env ETCD_ADVERTISE_CLIENT_URLS=http://0.0.0.0r:2379 \
bitnami/etcd:latest
docker network
の作成に関しては、この資料を参照
なお、macOSだと1000番未満のポートにアタッチできないので、ポート番号を変えてます。
ポート番号を変えたことによって、正しく動作しなくなるのでユーザー管理モジュールと注文管理モジュールの設定を変更する必要があります。
@@ -2,5 +2,5 @@ Name: user.rpc
ListenOn: 0.0.0.0:8080
Etcd:
Hosts:
- - 127.0.0.1:2379
+ - 127.0.0.1:12379
Key: user.rpc
@@ -4,5 +4,5 @@ Port: 8888
UserRpc:
Etcd:
Hosts:
- - 127.0.0.1:2379
+ - 127.0.0.1:12379
Key: user.rpc
ユーザー管理モジュールを起動する
go run ./mall/user/rpc/user.go -f ./mall/user/rpc/etc/user.yaml
注文管理モジュールを起動する
go run ./order/api/order.go -f ./order/api/etc/order.yaml
注文を取得してみる
curl -i -X GET http://localhost:8888/api/order/get/1
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Traceparent: 00-fd6d164ed3c5cf4b1dafc48faf2f5424-43071c77095d041d-00
Date: Wed, 31 May 2023 14:35:08 GMT
Content-Length: 30
{"id":"1","name":"test order"}%
以上!無事に取得までできた
一晩おいたら、ディレクトリ間違えていることに気付いた
.
├── README.md
├── go.mod
├── go.sum
├── mall
│ └── user
│ └── rpc
│ ├── etc
│ │ └── user.yaml
│ ├── internal
│ │ ├── config
│ │ │ └── config.go
│ │ ├── logic
│ │ │ └── getuserlogic.go
│ │ ├── server
│ │ │ └── userserver.go
│ │ └── svc
│ │ └── servicecontext.go
│ ├── types
│ │ └── user
│ │ ├── user.pb.go
│ │ └── user_grpc.pb.go
│ ├── user.go
│ ├── user.proto
│ └── userclient
│ └── user.go
└── order <-- 本来は、mallの配下にあるべき
└── api
├── etc
│ └── order.yaml
├── internal
│ ├── config
│ │ └── config.go
│ ├── handler
│ │ ├── getorderhandler.go
│ │ └── routes.go
│ ├── logic
│ │ └── getorderlogic.go
│ ├── svc
│ │ └── servicecontext.go
│ └── types
│ └── types.go
├── order.api
└── order.go
昨夜まで読めていた英語ドキュメントが読めなくなってしまった。。。
別のリンク
使ってみて良かったところ
- ベースファイル(APIやproto)作って、コマンドぽちぽちでapiサーバー立ち上げられるのは良さげ
- 誰がやっても同じようにサービスが作れる
- 細かいところに気を使わなくていい
- まだ使ったないけど、DDL作ったり、Dockerfile作ったりできる
- 設定ファイルの定義考えるの面倒だけど、枠組みできてるから楽
使ってみて改善したいところ
- いちいちディレクトリを変更しないとだめなのでちょっと面倒
- 生成されたコードでlinter通そうとすると設定が面倒
- ないものねだりだけど、テストコードも書いてくれると助かるのに
以上。
ほかにも出てくると思われるが、Getting Started やっただけだとこれくらい