Go言語を勉強してみる。
JavascriptとかPHPとか自分がよく知っている言語以外で、あたらしいモノに触れてないなと思い、今になってGoogleが開発したというGoに触れてみている。いろいろ調べていく中でおなじくGoogleが開発したというgRPCというのが出てきたので、どんなものか自分でも実装してみる。
サーバー/クライアントともGo(あるいはNode.js)という記事はいくつかあったが、違うサービス間で通信してるっぽくなるように、あえて違う言語で試した。
Nodeは元からホストにインストールしていたものの、Goはインストールしてなかったので、ここではDockerコンテナを用意した。
全体の構成
Dockerコンテナ側に配置するサーバープログラム。
root/
├─docker-compose.yml
├─Dockerfile
├─.env
└─src/
├─go.mod
└─gRPC/
├─grpc_sample/
├─server.go
└─ex.proto
ホスト側に配置するクライアントプログラム。
この2ファイルは同じディレクトリにあればよい
ex.proto
client.mjs
※ ex.proto の中身は両方とも同じ
各種ファイル
docker-compose.yml
services:
app:
container_name: app
build:
context: .
volumes:
- ./src:/go/src
tty: true
env_file: .env
environment:
- TZ=Asia/Tokyo
working_dir: /go/src
ports:
- 9000:9000
Dockerfile
# goバージョン
FROM golang:1.22.3-alpine
# アップデートとgit,protocのインストール
RUN apk update && apk add git protoc
# ワーキングディレクトリの設定
WORKDIR /go/src
# ホストのファイルをコンテナの作業ディレクトリに移行
ADD . /go/src
# パッケージのインストール
RUN go install golang.org/x/tools/cmd/goimports@latest
RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
RUN go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
.env
GOPATH=/go
go.mod
module src
go 1.22.3
ex.proto
syntax = "proto3";
package grpc_ex;
option go_package = "./grpc_sample";
message RequestMessage {
string request_id = 1;
}
message UserProfile {
string user_name = 1;
int32 user_age = 2;
}
message ResponseMessage {
repeated UserProfile user_datas = 1;
}
service ExService {
rpc GetProfile(RequestMessage) returns (ResponseMessage) {}
}
サーバー側(Go)
server.go
package main
import (
"fmt"
"log"
"net"
"src/gRPC/grpc_sample"
"golang.org/x/net/context"
"google.golang.org/grpc"
)
type GetProfileStruct struct {
name string
}
func (g *GetProfileStruct) GetProfile(ctx context.Context, req *grpc_sample.RequestMessage) (*grpc_sample.ResponseMessage, error) {
fmt.Println("Received request_id:", req.RequestId)
userDatas := []*grpc_sample.UserProfile{
// ユーザーデータのサンプル
{UserName: "ユーザーA", UserAge: 20},
{UserName: "ユーザーB", UserAge: 25},
{UserName: "ユーザーC", UserAge: 30},
}
// レスポンスを返す。
response := &grpc_sample.ResponseMessage{
UserDatas: userDatas,
}
return response, nil
}
func main() {
// 9000番ポートでクライアントからのリクエストを受け付ける。
listen, err := net.Listen("tcp", ":9000")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
grpcServer := grpc.NewServer()
// GetProfileStruct構造体のアドレスを通じてGetProfileメソッドが呼ばれるようになる。
grpc_sample.RegisterExServiceServer(grpcServer, &GetProfileStruct{})
fmt.Println("main start")
// リッスンする。
if err := grpcServer.Serve(listen); err != nil {
log.Fatalf("failed to serve: %s", err)
}
fmt.Println("main end")
}
クライアント側(Node.js)
client.mjs
import { loadSync } from '@grpc/proto-loader';
import { loadPackageDefinition, credentials } from '@grpc/grpc-js';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const protoFile = `${__dirname}/ex.proto`;
const pd = loadSync(protoFile);
const proto = loadPackageDefinition(pd);
const client =
new proto.grpc_ex.ExService('localhost:9000', credentials.createInsecure());
// 関数の戻り値はコールバックでしか受け取れない(?)のでPromiseにする。
const promisify = (methodName) =>
args => new Promise((resolve, reject) =>
client[methodName](args, (err, res) => {
if (err) { reject(err); }
else { resolve(res); }
})
);
const getProfile = promisify('GetProfile');
try {
const response = await getProfile({ requestId: '3072' });
console.log('response:', response);
} catch (e) {
console.error(e);
}
実際に動かしてみる
サーバープログラム
コンテナを立ち上げる。
# コンテナを起動
docker-compose up -d --build
# 起動したらシェルを実行する。
docker-compose exec app sh
↓コンテナ内のシェルで実行する。
cd /go/src/gRPC/
# プロトコル定義ファイルからGo向けのソースコードを生成する。
protoc --go_out=. --go-grpc_out=require_unimplemented_servers=false:. ex.proto
# ソースコードがgrpc_sample/直下に生成される。
ls grpc_sample/
go run server.go
# main start が出力されたらOK
クライアントプログラム
client.mjsを配置したディレクトリで、必要なモジュールをインストール
npm install @grpc/proto-loader @grpc/grpc-js
インストール完了したら実行
node client.mjs
実行結果
サーバープログラム
クライアントプログラム
参考情報
以下を参考にさせていただきしました。ありがとうございます。
最後に
身の回りでgRPCが活躍することは当分ないだろうけど、Goは使いこなせるくらいには習熟していきたい。
Webサーバーとしての用途で実装をすすめたら、難しいコトを覚える機会はありそう。