Quantcast
Channel: fukuoka.ex Elixir/Phoenix Advent Calendarの記事 - Qiita
Viewing all articles
Browse latest Browse all 25

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

$
0
0

(この記事は、「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アプリを作った」です。お楽しみに!


Viewing all articles
Browse latest Browse all 25

Trending Articles