バイナリデータをクラウドとやり取りする際の設計アプローチ

# 概要

バイナリデータをクラウドとやり取りする際、データフォーマットの設計が必要になりますが、色々な考え方が存在するため、それをまとめています

# TL;DR

| 方式      | データサイズ                      | センサー出力データの仕様変更時のプログラム更新 | メタデータ付与や集計等の処理 | クラウドとの相性 | 
|----------|--------------------------------|---------------------------------------|-----------------------|--------------|
| バイナリデータ直接      | オリジナル                       | 原則不要                               | ほぼ不可能               | 悪い         |
| Base64化 | バイナリデータの133% + シリアライズ書式 | 不要な場合がほとんど                      | 可能                   | 良い          |
| 構造化    | バイナリデータの数倍                | ほぼ必ず発生                            | 可能                   | 極めて良い     |

# 設計方針

バイナリデータを直接送る/シリアライズする、する場合の方式について考えると３通りの設計が存在します

1. バイナリデータを直接送信する
2. バイナリデータをシリアライズ
    1. Base64化
    2. 構造化

※シリアライズに関しては、JSONを前提としていますが、XMLやCSVといったことでも同じです

## バイナリデータを直接送信する

何も処理を行わず、クラウドへバイナリデータを直接送信する方法です
単純明快ですね。

### "クラウドとの相性が悪い"理由

クラウド側のAPIがREST(JSON)であることが多いためです。
たとえばAWS IoTはバイナリデータの受信をしてくれますが、その先のAWS LambdaやAmazon Elasticsearch ServiceといったサービスはJSONを要求します。

### それでもバイナリを送りたい！

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">送り手と受け手両方作る立場ならプロトコルバッファで、事足りるのかしらrtバイナリデータをクラウドとやり取りする際の設計アプローチ by <a href="https://twitter.com/ma2shita">@ma2shita</a> on <a href="https://twitter.com/Qiita">@Qiita</a> <a href="https://t.co/siZBDSfczJ">https://t.co/siZBDSfczJ</a></p>&mdash; ＞ω＜ (@u_akihiro) <a href="https://twitter.com/u_akihiro/status/829929492474851331">2017年2月10日</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

送信側と受信側(最終処理を行うアプリケーション)が完全に掌握できる環境であれば、バイナリでも無問題です。

## バイナリデータのシリアライズ

バイナリデータをシリアライズ方法には２つあります

### 1. Base64化

入ってきたバイナリ列をBase64エンコーディングし、単一のキー/値に押し込める方法です

```:元データ
0x02 0xFF 0xFF 0x00 0x03 0x4c 0x04
```

上記のようなデータを、下記のようにBase64エンコードします

```json
{
  "b64_str": "Av//AANMBA=="
}
```

#### おまけ: MessagePackやProtocol Buffersを使う

[Protocol Buffers](https://developers.google.com/protocol-buffers/) や [MessagePack](http://msgpack.org/) を使えば、Base64エンコーディングせずともバイナリデータをシリアライズすることが可能です

```ruby:Ruby+MessagePackでやってみる
# ----
## シリアライズ
payload = [0x02, 0xFF, 0xFF, 0x00, 0x03, 0x4c, 0x04].map(&:chr).join
=> "\x02\xFF\xFF\x00\x03L\x04"
require "msgpack"
serialized_data = [payload].to_msgpack
=> "\x91\xC4\a\x02\xFF\xFF\x00\x03L\x04"
# ----
## デシリアライズ
MessagePack.unpack(serialized_data).first
=> "\x02\xFF\xFF\x00\x03L\x04"
```

Base64エンコーディングに比べると、元のデータサイズ＋MessagePackのヘッダ等で済むため、コンパクトになります。
加えて構造化データですので、追加のメタデータを加えることも容易です。

クラウド側においては、Base64でもMessagePack等でも結局デシリアライズしなければならないので、それならMessagePack等でも良いよね、という考え方もあります。

```ruby:データサイズの比較
payload.length
=> 7
serialized_data.length
=> 10
```

### 2. 構造化

仕様を読み解き、データ構造に落としこむ方法です

```:元データ
0x02 0xFF 0xFF 0x00 0x03 0x4c 0x04
```

上記のようなデータを、下記のように構造化します

```json
{
  "signal_status": {
    "red": "on",
    "yellow": "on",
    "green": "off"
  }
}
```

# 設計のポイント

* データサイズ
* シリアライズ
    * 実行場所、プログラム更新
    * メタデータ、集計・蓄積等のビジネス要件処理の有無
* クラウドとの相性
    * 受信可能フォーマット
    * 受信データの処理

## データサイズ

データサイズは通信量/料に直接影響します。そのため、小さければ小さいほど良いと言えるでしょう。
また、一度に送信できるデータサイズの制限がクラウド側に存在することが多いので、それを回避する必要もあります

e.g.) AWS IoTは[１回に送信可能なデータサイズは128KBです](http://docs.aws.amazon.com/ja_jp/iot/latest/developerguide/iot-limits.html)

## シリアライズ

シリアライズは主にゲートウェイ等のエッジ側デバイスで行うことになるため、ほぼすべてのケースでプログラムを配置する必要があります。それにより物理的な設置場所のみならず対象台数も考慮する必要があり、主にセンサー側の出力データの仕様変更がされた場合において、プログラムの更新が発生しますが、その方法も初期から検討しておく必要があるでしょう。

## クラウドとの相性

クラウドとの相性においてはシリアライズ（特にJSON）であれば特に問題となることは無いのですが、バイナリの場合において受信ができない、処理ができないといった問題が発生する可能性があります。
具体的には、AWS IoTはバイナリデータを受信可能ですが、AWS Lambdaが受け取れるデータフォーマットがJSONのみであるため、AWS IoTから直接AWS Lambdaを呼び出すことができません。
※この件については[バイナリのデータをAWS IoT上でBase64エンコード＆JSON化して、後方サービスへ流す](http://qiita.com/ma2shita/items/f7cbc395464c483e232d)ことができるため、回避できます

# あとがき

オヌヌメはBase64化です

## おまけ

参考; RubyでワンライナーBase64

```ruby
Base64.strict_encode64([0x02, 0xFF, 0xFF, 0x00, 0x03, 0x4c, 0x04].map(&:chr).join)
```