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

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

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

# この記事はなに？
ElixirでProtocol Buffersを扱うプラグインが複数あったので、メジャーどころを纏めました！

Protocol Buffersについては下記の記事がわかりやすいです！
[今さらProtocol Buffersと、手に馴染む道具の話](https://qiita.com/yugui/items/160737021d25d761b353)

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

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


# 比較するプラグイン
今回は以下のメジャーどころのプラグインを比較します。
[exprotobuf](https://github.com/bitwalker/exprotobuf)
[protobuf-elixir](https://github.com/tony612/protobuf-elixir)

# 題材とするprotoの構成
以下の構成で検証していきます。

```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の検証

## セットアップ

### プラグインの追加

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

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

```shell:shell
$ brew install protobuf
```

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

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

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


```elixir: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``メソッドを使って値を入れていきます。

```elixir
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``を使います。

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

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

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


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

## セットアップ

### プラグインの追加
```elixir:mix.exs
def application do
  [
    applications: [:exprotobuf]
  ]
end

defp deps do
  [
    {:exprotobuf, "~> 1.2.9"}
  ]
end
```
### protoファイルを読み込むmoduleの作成
以下のように使いたいprotoファイルをmoduleで読み込みます。

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

# encode/decodeしてみる
## encode
ここら先は``protobuf-elixir``と同じ手順です。

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

```elixir
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``と同じ手順です。

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

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

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

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

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