Go、Node.jsのプログラム間でRPC通信をする


概要

gRPCを使用して、Go、Node.jsのプログラム間でRPC通信をします。
クライアント側をGo、サーバ側をNode.jsが担当します。

環境
MacOS Catalina: 10.15.1
Go: 1.13.4
Node.js: 10.15.3

クライアント(Go)の作成

クライアント側のディレクトリを作成します。

$ mkdir grpc-test-go

クライアント側のディレクトリ構成は最終的に以下のようになります。

$ cd grpc-test-go
$ tree
.
├── bridge
│   ├── bridge.pb.go
│   ├── bridge.proto
│   └── go.mod
├── client.go
└── go.mod

.protoファイルの作成

protoファイルを作成して、仕様を定義します。
型にrepeatedをつけると配列になります。
公式ページを参照してください。

bridge.proto
syntax = "proto3";

package bridge;

service BridgeService {
    rpc PostData (Data) returns (Reply) {}
} 

message Data {
    string key = 1;
    repeated string data = 2;
}

message Reply {
    string response = 1;
}

.protoファイルからコードを生成

定義した.protoファイルからクライアント、サーバー共通で使用するコードを生成します。
まず、コードを生成するために必要なprotobufパッケージをインストールします。

$ brew install protobuf
$ protoc bridge/bridge.proto --go_out=plugins=grpc:.

これで、bridge.pb.goが作成されました

module周りを整理

ローカルでbridge.pb.goを参照したいので、色々します。

$ go mod init grpc-test-go
$ cd bridge 
$ go mod init bridge

grpc-test-goの方のgo.modファイルを編集

go.mod
module grpc-test-go

go 1.13

require (
    github.com/[username]/grpc-test2/bridge v0.0.0
    google.golang.org/grpc v1.25.1
)

replace github.com/[username]/grpc-test-go/bridge => ./bridge

クライアント側コードの作成

client.go
package main

import (
    "context"
    "fmt"
    "google.golang.org/grpc"
    pb "github.com/melonattacker/grpc-test-go/bridge"
)

func RpcPost(key string, Data []string) (string, error) {
    conn, err := grpc.Dial("127.0.0.1:50051", grpc.WithInsecure())
    if err != nil {
        return "", err
    }
    defer conn.Close()
    client := pb.NewBridgeServiceClient(conn)
    message := &pb.Data{Key: key, Data: Data}
    res, err := client.PostData(context.TODO(), message)
    response := res.Response
    if err != nil {
        return "", err
    }
    return response, nil
}

func main() {
    data := []string{"apple", "orange", "lemon"}
    result, err := RpcPost("fruit", data);
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println(result)
}
$ go build

コンパイルが通るはずです。

サーバ(Node.js)の作成

サーバ側のディレクトリを作成します。
クライアント側と依存しない形で作成しましょう。

$ mkdir grps-test-node
$ cd grps-test-node

.protoファイルの作成

上で作成したbridge.protoをコピーしてきます。

bridge.proto
syntax = "proto3";

package bridge;

service BridgeService {
    rpc PostData (Data) returns (Reply) {}
} 

message Data {
    string key = 1;
    repeated string data = 2;
}

message Reply {
    string response = 1;
}

必要なnpmパッケージのインストール

$ npm init -y
$ npm install grpc @grpc/proto-loader --save

サーバ側コードの作成

server.js
const grpc = require('grpc')
const protoLoader = require('@grpc/proto-loader')
const PROTO_PATH = __dirname + '/bridge.proto'

const packageDefinition = protoLoader.loadSync(
    PROTO_PATH,
    {
        keepCase: true,
        longs: String,
        enums: String,
        defaults: true,
        oneofs: true
    }
)

const BridgeProto = grpc.loadPackageDefinition(packageDefinition)

const server = new grpc.Server()

const PostData = (call, callback) => {
    console.log(call.request);
    callback(null, { response: "Data was sent to server with key: " + call.request.key })
}

server.addService(BridgeProto.bridge.BridgeService.service, {
    PostData: PostData,
})

server.bind('127.0.0.1:50051', grpc.ServerCredentials.createInsecure())
console.log('Listening on 127.0.0.1:50051...')
server.start()

実行

サーバ(Node.js)

$ cd grpc-test-node
$ node main.js
Listening on 127.0.0.1:50051...

クライアント(Go)

$ cd grpc-test-go
$ go run client.go

サーバ(Node.js)

{ data: [ 'apple', 'orange', 'lemon' ], key: 'fruit' }

クライアント(Go)

Data was sent to server with key: fruit

無事データが送られました!
以上です!