Help us understand the problem. What is going on with this article?

Protocol BuffersをElixirで使うためにメジャーどころのプラグインを実装・比較してみた!(protobuf-elixir / exprotobuf)

More than 1 year has passed since last update.

はじめに

この記事はミクシィグループ Advent Calendar 2018 17日の記事です。
https://qiita.com/advent-calendar/2018/mixi

最近Elixirを触り始めました。Elixir楽しい!

この記事はなに?

ElixirでProtocol Buffersを扱うプラグインが複数あったので、メジャーどころを纏めました!

Protocol Buffersについては下記の記事がわかりやすいです!
今さらProtocol Buffersと、手に馴染む道具の話

環境

今回の記事は以下の環境で検証しています。

  • MacOS HighSierra
  • Erlang 9.3.1
  • Elixir 1.6.5
  • Phoenix 1.4.0

比較するプラグイン

今回は以下のメジャーどころのプラグインを比較します。
exprotobuf
protobuf-elixir

題材とするprotoの構成

以下の構成で検証していきます。

user_list.proto
syntax = "proto3";
package protobuf;

message UserList {
  repeated User user_list = 1;
  message User {
    uint64 id = 1;
    string name = 2;
    Address address = 3;
    message Address {
      string addr1 = 1;
      string addr2 = 2;
    }
  }
}

protobuf-elixirの検証

セットアップ

プラグインの追加

mix.exs
defp deps do
[
   {:protobuf, "~> 0.5.3"},
   {:google_protos, "~> 0.1"}
]
end

protoコマンドのインストール

protobuf-elixirの場合Elixirファイルを生成する必要があるのでprotoコマンドをインストールします。
Macの場合以下のコマンドでインストール

shell
$ brew install protobuf

以下のコマンドでprotoファイルから.exファイルをジェネレートします。

shell
$ protoc --elixir_out=./ user_list.proto

すると以下のようなファイルが自動生成されます。

user_list.pb.ex
defmodule Protobuf.UserList do
  @moduledoc false
  use Protobuf, syntax: :proto3

  @type t :: %__MODULE__{
          user_list: [Protobuf.UserList.User.t()]
        }
  defstruct [:user_list]

  field :user_list, 1, repeated: true, type: Protobuf.UserList.User
end

defmodule Protobuf.UserList.User do
  @moduledoc false
  use Protobuf, syntax: :proto3

  @type t :: %__MODULE__{
          id: non_neg_integer,
          name: String.t(),
          address: Protobuf.UserList.User.Address.t()
        }
  defstruct [:id, :name, :address]

  field :id, 1, type: :uint64
  field :name, 2, type: :string
  field :address, 3, type: Protobuf.UserList.User.Address
end

defmodule Protobuf.UserList.User.Address do
  @moduledoc false
  use Protobuf, syntax: :proto3

  @type t :: %__MODULE__{
          addr1: String.t(),
          addr2: String.t()
        }
  defstruct [:addr1, :addr2]

  field :addr1, 1, type: :string
  field :addr2, 2, type: :string
end

上記の自動生成ファイルを使ってencode, decodeを行います。

encode/decodeしてみる

encode

encodeを行うには各Module毎にnewメソッドを使って値を入れていきます。

user_list = [
  # Userに対し値を入れていく
  Protobuf.UserList.User.new(
    id: 1,
    name: "ヤマダ",
    # AddressはUserの入れ子なので、ここでもnewを宣言する。
    address:
      Protobuf.UserList.User.Address.new(
        addr1: "東京都千代田区",
        addr2: "0-0-0"
      )
  ),
  Protobuf.UserList.User.new(
    id: 2,
    name: "タナカ",
    address:
      Protobuf.UserList.User.Address.new(
        addr1: "東京都渋谷区",
        addr2: "1-1-1"
      )
  )
]

# トップレベルは配列なのでのUserListに上記で作成した、Userの配列を入れる。
user = Protobuf.UserList.new(user_list: user_list)
# binaryに変換
user_binary = Protobuf.UserList.encode(user) # <<10, 45, 8, 1, 18, 9, 227, 131, 164, 227, 131, 158, ...>>

上記のように各Moduleに対して値を入れていきます。
それらを encodeに入れることで、バイナリへと変換することが出来ます。

decode

decoodeは先程作成したバイナリをそのままdecodeを使います。

# 先程作成したbinaryをdecodeに入れる
Protobuf.UserList.decode(user_binary)

上記のように要素のトップレベルのModuleにbinaryを入れることで、復元することが出来ます。

所感

思ったよりも簡単に実装することが出来ました!ただ、protobuf-elixirの場合、
手元の環境でElixir用に自動生成ファイルを作成する必要があるので、
チーム開発をおこなう際にメンバーの環境だったり、
生成したファイルの管理だったりとそこら辺の管理をちゃんと気にしてあげる必要があるなと感じました。

exprotobufの検証

exprotobufはprotoファイルを読み込んで自動生成ファイルを作成する必要がありません。
Elixir上に直接protoファイルを読み込んで使います。

セットアップ

プラグインの追加

mix.exs
def application do
  [
    applications: [:exprotobuf]
  ]
end

defp deps do
  [
    {:exprotobuf, "~> 1.2.9"}
  ]
end

protoファイルを読み込むmoduleの作成

以下のように使いたいprotoファイルをmoduleで読み込みます。

protobuf.ex
defmodule Protobuf do
  use Protobuf, from: Path.expand("./user_list.proto", __DIR__)
end

encode/decodeしてみる

encode

ここら先はprotobuf-elixirと同じ手順です。

encodeを行うには各Module毎にnewメソッドを使って値を入れていきます。

user_list = [
  # Userに対し値を入れていく
  Protobuf.UserList.User.new(
    id: 1,
    name: "ヤマダ",
    # AddressはUserの入れ子なので、ここでもnewを宣言する。
    address:
      Protobuf.UserList.User.Address.new(
        addr1: "東京都千代田区",
        addr2: "0-0-0"
      )
  ),
  Protobuf.UserList.User.new(
    id: 2,
    name: "タナカ",
    address:
      Protobuf.UserList.User.Address.new(
        addr1: "東京都渋谷区",
        addr2: "1-1-1"
      )
  )
]

# トップレベルは配列なのでのUserListに上記で作成した、Userの配列を入れる。
user = Protobuf.UserList.new(user_list: user_list)
# binaryに変換
user_binary = Protobuf.UserList.encode(user) # <<10, 45, 8, 1, 18, 9, 227, 131, 164, 227, 131, 158, ...>>

上記のように各Moduleに対して値を入れていきます。
それらを encodeに入れることで、バイナリへと変換することが出来ます。

decode

こちらもprotobuf-elixirと同じ手順です。

# 先程作成したbinaryをdecodeに入れる
Protobuf.UserList.decode(user_binary)

上記のように要素のトップレベルのModuleにbinaryを入れることで、復元することが出来ます。

所感

protobuf-elixir同様こちらも思ったより簡単に実装することが出来ました!
しかも、exprotobufの場合はファイルを自動生成せず、protoを直接読み込むことが出来るので、
チーム開発においてもそれほど障壁なく導入することが出来そうだなと感じました!

まとめ

ElixirでProtocol Buffersを使う際に使うプラグインのメジャーどころを比較してみました!
両プラグイン共学習コストも低くサクッと導入することが出来てとても良かったです!
特に、exprotobufに関してはファイルの自動生成も必要ないのでチーム開発でも問題なく導入できそうです!

以上になります。最後まで読んでいただきありがとう御座いました!

kakky0418
Golangに入門
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away