0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

gRPCの通信をGo(サーバー)とNode.js(クライアント)で実装してみる。

Posted at

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

実行結果

サーバープログラム

server.png

クライアントプログラム

client.png

参考情報

以下を参考にさせていただきしました。ありがとうございます。

最後に

身の回りでgRPCが活躍することは当分ないだろうけど、Goは使いこなせるくらいには習熟していきたい。
Webサーバーとしての用途で実装をすすめたら、難しいコトを覚える機会はありそう。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?