go-zero - Golang learning step 10-4

  • 公開日
  • カテゴリ:ToolForMicroservices
  • タグ:Golang,roadmap.sh,学習メモ
go-zero - Golang learning step 10-4

roadmap.sh > Go > Tool for Microservices > go-zero の学習を進めていきます。

※ 学習メモとしての記録ですが、後にこのセクションを学ぶ道しるべとなるよう、ですます調で記載しています。

contents

  1. 開発環境
  2. 参考 URL
  3. go-zero
  4. インストール
  5. API の作成
  6. ミドルウェア
    1. ミドルウェアの実装
    2. 動作確認
  7. gRPC サーバー構築
    1. etcd の設定と省略方法

開発環境

  • チップ: Apple M2 Pro
  • OS: macOS Sonoma
  • go version: go1.23.2 darwin/arm64

参考 URL

go-zero

go-zero は、軽量で効率的な Go 向けの API フレームワーク です。 マイクロサービスの開発を簡単にし、パフォーマンスを最適化するための機能が組み込まれています。

  • 高パフォーマンス:ゼロコストの RPC を実現する zrpc を採用。
  • シンプルなコード生成:goctl を使用して自動的に API コードを生成できる。
  • 組み込みミドルウェア:認証やロギングなどが簡単に設定可能。
  • 統合された RPC & REST API:REST と gRPC の両方をサポート。

インストール

プロジェクトの初期化

mkdir go_zero_sample
cd go_zero_sample
go mod init go_zero_sample

go-zero では goctl コマンドを使ってファイル生成を行うためこれをインストールします。

go install github.com/zeromicro/go-zero/tools/goctl@latest

go-zero をインストールします。

go get -u github.com/zeromicro/go-zero

API の作成

goctl コマンドを使って API を作成します。

.api ファイル作成

goctl api -o user/user.api

goctl コマンドで生成された user/user.api を編集してエンドポイントを定義します。

syntax = "v1"

type getUserRequest {
  UserId string `path:"userId"`
}

type getUserResponse {
  Message string `json:"message"`
}

service user-api {
  @handler GetUser
  get /users/id/:userId (getUserRequest) returns (getUserResponse)
}

以下 goctl コマンドで Go のサーバーサイドコードを生成します。

goctl api go -api user/user.api -dir user

以下ファイル群が生成されます。

user
├── etc
│    └── user-api.yaml
├── internal
│    ├── config
│    │   └── config.go
│    ├── handler
│    │   ├── getuserhandler.go
│    │   └── routes.go
│    ├── logic
│    │   └── getuserlogic.go
│    ├── svc
│    │   └── servicecontext.go
│    └── types
│     └── types.go
├── user.api
└── user.go

user/internal/logic/getuserlogic.go に GetUser を実装します。

func (l *GetUserLogic) GetUser(req *types.GetUserRequest) (resp *types.GetUserResponse, err error) {
  return &types.GetUserResponse{
    Message: fmt.Sprintf("Hello World, UserId: %s", req.UserId),
  }, nil
}

ここまでやるとエンドポイントが動作するので実際に確認してみます。

user-api サーバーを起動させます。

cd user
go mod tidy

go run user.go -f etc/user-api.yaml

エンドポイントにリクエストを送信します。

curl -i http://localhost:8888/users/id/123

レスポンス:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Traceparent: xxxxx
Date: Tue, 11 Feb 2025 23:53:03 GMT
Content-Length: 38

{"message":"Hello World, UserId: 123"}

ミドルウェア

ミドルウェアは、リクエストとレスポンスの間に処理を挟む仕組みです。

例えば、以下のような処理を自動化できます。

  • ロギング(リクエストの記録)
  • 認証(JWT の検証)
  • リクエストの前処理(ヘッダーの検査)
  • レスポンスの後処理(データの圧縮)

ミドルウェアの実装

参考: https://go-zero.dev/en/docs/tutorials/api/middleware

リクエストの開始時刻を記録し、処理が完了するまでにかかった時間(Duration)をログに出力するミドルウェア RequestDurationLogger を作成していきます。

user/user.api にミドルウェアを宣言します。

syntax = "v1"

type getUserRequest {
  UserId string `path:"userId"`
}

type getUserResponse {
  Message string `json:"message"`
}

// ミドルウェアを宣言
@server (
  middleware: RequestDurationLoggerMiddleware
)

service user-api {
  @handler GetUser
  get /users/id/:userId (getUserRequest) returns (getUserResponse)
}

以下 goctl コマンドで Go のサーバーサイドコードを生成します。

goctl api go -api user/user.api -dir user

user/internal/middlewarerequestdurationloggermiddleware.go が生成されますので、Handle メソッドを実装していきます。

package middleware

import (
  "log"
  "net/http"
  "time"
)

type RequestDurationLoggerMiddleware struct {
}

func NewRequestDurationLoggerMiddleware() *RequestDurationLoggerMiddleware {
  return &RequestDurationLoggerMiddleware{}
}

func (m *RequestDurationLoggerMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc {
  return func(w http.ResponseWriter, r *http.Request) {
    start := time.Now() // 処理開始時間を記録

    // 次のハンドラーを実行
    next(w, r)

    // 処理時間を計測
    duration := time.Since(start)

    // ログに出力
    log.Printf("[RequestDuration] %s %s took %v", r.Method, r.URL.Path, duration)
  }
}
  • Handle メソッドでリクエストを受け取る。
  • start := time.Now() で開始時間を記録。
  • next(w, r) を呼ぶことで、次の処理(API ハンドラー)にリクエストを渡す。
  • time.Since(start) で処理時間を測定し、ログを出力。

動作確認

user-api サーバーを起動し、ミドルウェア RequestDurationLogger の動作確認を行います。

cd user
go run user.go -f etc/user-api.yaml

エンドポイントにリクエストを送信します。

curl -i http://localhost:8888/users/id/123

以下が出力されるようになります。

2025/02/12 22:28:26 [RequestDuration] GET /users/id/123 took 466.709µs

gRPC サーバー構築

次は gRPC サーバーを構築してみます。

参考: https://go-zero.dev/en/docs/tutorials/grpc/server/example

proto ファイルを生成します。

goctl rpc -o greet/greet.proto

生成された proto ファイルは以下のようになっています。

syntax = "proto3";

package greet;
option go_package="./greet";

message Request {
  string ping = 1;
}

message Response {
  string pong = 1;
}

service Greet {
  rpc Ping(Request) returns(Response);
}

今回はこのままでいきます。

次に、この proto ファイルを元に Goサーバーサイドコードを生成します。

goctl rpc protoc greet/greet.proto --go_out=./greet  --go-grpc_out=./greet  --zrpc_out=./greet

greet/ 配下に以下ファイル群が生成されます。

greet
├── etc
│   └── greet.yaml
├── greet
│   ├── greet.pb.go
│   └── greet_grpc.pb.go
├── greet.go
├── greet.proto
├── greetclient
│   └── greet.go
└── internal
    ├── config
    │   └── config.go
    ├── logic
    │   └── pinglogic.go
    ├── server
    │   └── greetserver.go
    └── svc
        └── servicecontext.go

greet/internal/logic/pinglogic.goPing メソッドを実装します。

func (l *PingLogic) Ping(in *greet.Request) (*greet.Response, error) {
    fmt.Printf("in %#v\n", in)
	return &greet.Response{
        Pong: "pong",
    }, nil
}

gRPC Server Reflection を有効にするため、greet/etc/greet.yaml で開発モードを指定します。

Name: greet.rpc
ListenOn: 0.0.0.0:8080
Mode: dev # 追加
Etcd:
  Hosts:
  - 127.0.0.1:2379
  Key: greet.rpc

gRPC サーバーを起動し、リクエストを送信します。

# 初回のみ
cd greet
go mod tidy

# gRPC サーバーを起動
go run greet.go -f etc/greet.yaml

# サービス一覧
grpcurl localhost:8080 list

greet.Greet
grpc.health.v1.Health
grpc.reflection.v1.ServerReflection


# メソッド一覧
grpcurl -plaintext localhost:8080 list greet.Greet

greet.Greet.Ping


# メソッドの詳細情報
grpcurl -plaintext localhost:8080 describe greet.Greet.Ping

greet.Greet.Ping is a method:
rpc Ping ( .greet.Request ) returns ( .greet.Response );


# 実際に gRPC リクエストを送信

grpcurl -plaintext localhost:8080 greet.Greet.Ping
{
  "pong": "pong"
}

etcd の設定と省略方法

greet/etc/greet.yaml にはデフォルトで etcd の設定が記述されています

# etcd の設定
Etcd:
  Hosts:
  - 127.0.0.1:2379
  Key: greet.rpc

この設定が有効なままだと、gRPC サーバーの起動時に etcd への接続が求められます。

etcd がインストールされていないと、以下のようなエラーが発生します。

rpc error: code = DeadlineExceeded desc = latest balancer error: last connection error:
connection error: desc = "transport: Error while dialing: dial tcp 127.0.0.1:2379: connect: connection refused"

etcd をインストールする方法

Mac の場合、Homebrew を使用して etcd をインストールできます。

brew install etcd

etcd を不要にする方法

etcd なしで gRPC サーバーを起動する場合は、以下のように Hosts を空のリストにし、Key を "" に設定します。

# etcd を無効化する設定
Etcd:
  Hosts: []
  Key: ""

この設定を適用すれば、etcd をインストールせずに gRPC サーバーを起動できます。

まとめ

この記事では、Go のマイクロサービス向けフレームワーク go-zero の基本を学習しました。

  • goctl を使用した API コードの自動生成
  • go-zero による REST API の実装
  • ミドルウェアの導入方法
  • gRPC サーバーの構築手順
    • etcd を使用したサービス管理とその省略方法

go-zero のさらなる優秀な機能

  • モデル生成:SQL から Go のデータモデルを自動生成する goctl model
  • タスクキュー:非同期処理を簡単に管理できる queue パッケージ
  • 負荷分散:複数の RPC サーバー間でリクエストを分散処理
  • トレーシング:OpenTelemetry によるリクエストトレースのサポート
  • サービスリカバリ:クラッシュ時の自動回復機能
  • コンフィグ管理:環境変数や YAML による設定の柔軟な管理


[Next] Step 10-5: Watermill

[Prev] Step 10-3: Micro

Author

rito

  • Backend Engineer
  • Tokyo, Japan
  • PHP 5 技術者認定上級試験 認定者
  • 統計検定 3 級