8
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

fukuoka.ex Elixir/PhoenixAdvent Calendar 2018

Day 18

grpc-elixirでGoと通信してみる #2

Last updated at Posted at 2018-12-18

(この記事は、「fukuoka.ex Elixir/Phoenix Advent Calendar Advent Calendar 2018」の18日目です)

昨日は @artk さんの「GigalixirでSlackBotを動かしてみる」でした。本日は「grpc-elixirでGoと通信してみる #1」の続きです。

Route Guide サンプル

前回はGoとElixirのgRPC通信でhelloworldがうまくいきました。今回はroute_guideというサンプルを試します。

このサンプルは、ストリーミング(アップロード、ダウンロード、全二重)が含まれます。

この記事では触れませんが、grpcでストリーミングが必用になった場合はこのサンプルコードをあたると良いでしょう。

Elixirのroute_guideサンプルはこちら

Goのサンプルはこちらです。

プログラムのポイント

残念ながら現時点ではElixirのgRPCプログラミングガイドはないので、Goのものと見比べて実装を追う必要があります。

以下、双方向ストリーミングRPCのクライアントプログラムで見比べてみます。runRouteChatは、RPCのクライアントプログラムです。

Goではストリーミングにgoroutineを使って並列で処理をしています。

func runRouteChat(client pb.RouteGuideClient) {
	notes := []*pb.RouteNote{
		{Location: &pb.Point{Latitude: 0, Longitude: 1}, Message: "First message"},
		{Location: &pb.Point{Latitude: 0, Longitude: 2}, Message: "Second message"},
		{Location: &pb.Point{Latitude: 0, Longitude: 3}, Message: "Third message"},
		{Location: &pb.Point{Latitude: 0, Longitude: 1}, Message: "Fourth message"},
		{Location: &pb.Point{Latitude: 0, Longitude: 2}, Message: "Fifth message"},
		{Location: &pb.Point{Latitude: 0, Longitude: 3}, Message: "Sixth message"},
	}
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	stream, err := client.RouteChat(ctx)
	if err != nil {
		log.Fatalf("%v.RouteChat(_) = _, %v", client, err)
	}
	waitc := make(chan struct{})
	go func() {
		for {
			in, err := stream.Recv()
			if err == io.EOF {
				// read done.
				close(waitc)
				return
			}
			if err != nil {
				log.Fatalf("Failed to receive a note : %v", err)
			}
			log.Printf("Got message %s at point(%d, %d)", in.Message, in.Location.Latitude, in.Location.Longitude)
		}
	}()
	for _, note := range notes {
		if err := stream.Send(note); err != nil {
			log.Fatalf("Failed to send a note: %v", err)
		}
	}
	stream.CloseSend()
	<-waitc
}

一方Elixir側の対応する関数です。Task.async/Task.awaitで並列ストリーミングを行います。

 def run_route_chat(channel) do
    data = [
      %{lat: 0, long: 1, msg: "First message"},
      %{lat: 0, long: 2, msg: "Second message"},
      %{lat: 0, long: 3, msg: "Third message"},
      %{lat: 0, long: 1, msg: "Fourth message"},
      %{lat: 0, long: 2, msg: "Fifth message"},
      %{lat: 0, long: 3, msg: "Sixth message"}
    ]

    stream = channel |> Routeguide.RouteGuide.Stub.route_chat()

    notes =
      Enum.map(data, fn %{lat: lat, long: long, msg: msg} ->
        point = Routeguide.Point.new(latitude: lat, longitude: long)
        Routeguide.RouteNote.new(location: point, message: msg)
      end)

    task =
      Task.async(fn ->
        Enum.reduce(notes, notes, fn _, [note | tail] ->
          opts = if length(tail) == 0, do: [end_stream: true], else: []
          GRPC.Stub.send_request(stream, note, opts)
          tail
        end)
      end)

    {:ok, result_enum} = GRPC.Stub.recv(stream)
    Task.await(task)

    Enum.each(result_enum, fn {:ok, note} ->
      IO.puts(
        "Got message #{note.message} at point(#{note.location.latitude}, #{
          note.location.longitude
        })"
      )
    end)
  end

見比べてみてどうしたでしょうか? 関数名はGoの実装を踏襲してるため、一目でわかるのではないかと思います。(構造体の記述が多い分、Elixirのコードが長めになっていますね。)

前回の続き

さて、これまで説明してきたroute_guideサンプルでElixir-Go間の通信を行います。

コンテナから抜けて、2つのコンテナを終了しましょう。 docker-comopose downではコンテナの終了と破棄を同時に行ってくれます。

# exit
$ docker-compose down

Route Guide : Elixir -> Golang

Editorマークをクリックしてdocker-compose.ymlを編集をします。

image.png

docker-compose.ymlのcommandの#を外して、Saveボタンを忘れずに押してください。
(注記:command:の字下げは、build:のカラムに合わせてください)

  go-node:
    build:
      context: .
      dockerfile: Dockerfile-go
    command: sh -c "cd /go/src/google.golang.org/grpc/examples/route_guide/ && server/server"

これでGolang側のRoute Guideサーバー起動準備ができました。
以下、コンテナを立ち上げます。

※ エラーが出る場合は、エディターで右端が切れてないかをチェックしてみて下さい。

$ docker-compose up -d

docker psコマンドで、コンテナが2つ立ち上がってることを確認したら次へ進みます。コンテナが1つしかない場合は、docker-compose downでコンテナを落としてEditorでymlの編集内容を見直してください。

image.png

以下elixir-nodeのコンテナに入ります。

$ docker-compose exec elixir-node ash

今度はroute_guideのフォルダに移動、依存関係の取得・コンパイル。


# cd ~/grpc-elixir/examples/route_guide
# mix deps.get && mix compile

サーバー接続先を書き替えます

# vi priv/client.exs
client.exs
# 9行目
{:ok, channel} = GRPC.Stub.connect("localhost:10000", opts)
         ↓
{:ok, channel} = GRPC.Stub.connect("go-node:10000", opts)
# mix run priv/client.exs

実行すると、以下から始まる大量のテキストが流れれば接続は成功です。
ストリーミングを含む接続がうまく行ってるのが確認できました。

Getting feature for point (409146138, -746188906)
<name: "Berkshire Valley Management Area Trail, Jefferson, NJ, USA", location: <latitude: 409146138, longitude: -746188906>>
Getting feature for point (0, 0)
<name: nil, location: <latitude: 0, longitude: 0>>
Looking for features within %Routeguide.Rectangle{hi: <latitude: 420000000, longitude: -730000000>, lo: <latitude: 400000000, longitude: -750000000>}
<name: "Patriots Path, Mendham, NJ 07945, USA", location: <latitude: 407838351, longitude: -746143763>>
~ 略 ~

Route Guide : Golang -> Elixir

今度は逆方向をやってみます。

# mix grpc.server

これで、elixir-nodeのgRPCサーバーが起動するので、Ctrl+p Ctrl+qでコンテナからデタッチします。

go-nodeコンテナに入ります

$ docker-compose exec go-node bash

route_guideフォルダーに移動して

# cd /go/src/google.golang.org/grpc/examples/route_guide

route_guideクライアントをelixir-nodeに向けて実行します。こちらはhelloworldと違ってサーバーのオプション起動が付いているので楽です。
elixir-nodeのport=10000に対して、リクエストしてみます。

# client/client -server_addr elixir-node:10000

先ほどのように以下から始まるテキストが流れたら成功です。(Elixirと若干フォーマットが違いますね。)

2018/12/10 03:42:56 Getting feature for point (409146138, -746188906)
2018/12/10 03:42:56 name:"Berkshire Valley Management Area Trail, Jefferson, NJ, USA" location:<latitude:409146138 longitude:-746188906 >
2018/12/10 03:42:56 Getting feature for point (0, 0)
・・・・

以上で、公式サンプルのgRPC双方向通信が確認できました。

終了

以下でコンテナを抜けることができます。

# exit

作業が終了したら、左上の「CLOSE SESSION」ボタンを押すと、コンテナホストごと消去されます。

お疲れ様でした。


明日は@piacere_exさんの「BASIC以来、35年間プログラミングしてないIT企業社長が、ElixirでWebアプリを作った」です。お楽しみに!

8
5
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
8
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?