Protocol Buffersを使った所感

先日参加したインターンシップで **Protocol Buffers** を知ったので、実際に通信速度と容量の違いをJSON形式と比較してみました。

## Protocol Buffers とは
Wikipediaで検索すると、**構造データのシリアライズを目的とした技術スタック** という風に出てくるのですが、これではあまり意味が分かりませんね。（笑）自分もあまりこちらに関してはわかっていません。

自分が使ってみて実際に恩恵を受けたのは、JSONより通信容量が大きく、速度が速いことです。フォーマットがバイナリのため、通信データ量が削減されます。それに伴って、通信速度も上がります。

## 制作物概要
今回は通信容量と速度を知りたいので、バックエンドでモックデータを作成し、それをフロントで呼び出すだけのものを作ります。
フロントエンドは Next.js、バックエンドは Railsで開発します。

## 開発

### 1. バックエンド
#### Ⅰ. リポジトリから、.protoファイルを拝借
　送受信するデータのスキーマを定義したProtoファイルを取り込みます。もし、このサブモジュール（ある Git リポジトリの中の、別の Git リポジトリを含める仕組み）の中に、また別のサブモジュールが含まれている場合は、どちらのコマンドも実行します。
```:ターミナル
　git submodule add https://github.com/yumachin/proto.git proto
```
```:ターミナル
　git submodule update --init --recursive
```
#### Ⅱ. gem "google-protobuf" を追加 ⇒ bundle i
#### Ⅲ. protoc を使って、.protoファイルをRubyのクラスに変換
```:ターミナル
　protoc --ruby_out=./app ./proto/Proto/*.proto
```
#### Ⅳ. Protocol Bufferでレスを返すコントローラーを定義
```ruby: tasks_controller.rb
require_relative '../proto/Proto/task_pb'

class TasksController < ApplicationController
  def index
  # モックデータを100個ぐらい準備する
    tasks = [
      { email: "test1@test.com", companyName: "テスト1株式会社", deadline: "X月XX日", task: "YYY", submitTo: "ZZZ" },
    ]


    response = TaskResponse.new(tasks: tasks.map { |task| Task.new(task) })

    # .to_proto: protobuf 形式に変換
    render plain: response.to_proto, content_type: "application/x-protobuf"
  end
end
```
#### Ⅴ. JSONでレスを返すコントローラーを定義
```ruby: books_controller.rb
class BooksController < ApplicationController
  def index
  # モックデータを100個ぐらい準備する
    books = [
      { email: "test2@test.com", companyName: "テスト2株式会社", deadline: "X月XX日", task: "YYY", submitTo: "ZZZ" },
    ]
    render json: books
  end
end
```

### 2. フロントエンド
#### Ⅰ. リポジトリから、.proto ファイルを拝借
　バックエンドと同様です。
```:ターミナル
　git submodule add https://github.com/yumachin/proto.git proto
```
```:ターミナル
　git submodule update --init --recursive
```
#### Ⅱ. proto ファイルを JS や TS のコードに変換するためのツールをインストール
```:ターミナル
　npm i protobufjs-cli
```
#### Ⅲ. src ディレクトリに、generated ディレクトリを作成
#### Ⅳ. proto ファイルを JS や TS のコードに変換
```:ターミナル
npx pbjs --no-verify --no-delimited -t static-module -w es6 -o ./src/generated/protocol.js ./proto/Proto/*.proto && npx pbts -o ./src/generated/protocol.d.ts ./src/generated/protocol.js
```
#### Ⅴ: page.tsx を記述
```react:app / page.tsx
"use client";

import { useEffect, useState } from "react";
import { fetchBooks, fetchTasks } from "../utils/api";
import { ITask } from "@/generated/protocol";
// import { makeDummyTask } from "@/mock/mock";

export default function Home() {
  // モックデータ作成
  // const data = [ makeDummyTask(1), makeDummyTask(2), makeDummyTask(3)]

  // ➀ protobuf
  const [tasks, setTasks] = useState<ITask[]>([]);
  useEffect(() => {
    const getTasks = async () => {
      const tasks = await fetchTasks();
      setTasks(tasks);
    };
    getTasks();
  }, []);

  // ➁ JSON
  const [books, setBooks] = useState<Book[]>([]);
  useEffect(() => {
    const getBooks = async () => {
      const books = await fetchBooks();
      setBooks(books);
    };
    getBooks();
  }, []);

  return (
    <div>
      <h1>Tasks</h1>
      <ul>
        {tasks.map((task: ITask, index: number) => (
          <li key={index}>
            {task.companyName} / {task.email} / {task.deadline} / {task.task} / {task.submitTo} 
          </li>
        ))}
      </ul>
      <h1>Books</h1>
      <ul>
        {books.map((book: Book, index: number) => (
          <li key={index}>
            {book.companyName} / {book.email} / {book.deadline} / {book.task} / {book.submitTo} 
          </li>
        ))}
      </ul>
    </div>
  );
};

type Book = {
  email: string;
  companyName: string;
  deadline: string;
  task: string;
  submitTo: string;
}
```
#### Ⅵ. api を叩く関数 を定義
```react:utils / api.ts
import * as proto from "../generated/protocol";

export async function fetchTasks() {
  console.time("Proto の fetch 時間");
  const res = await fetch("http://localhost:3001/tasks", {
    headers: { Accept: "application/x-protobuf" },
    cache: "no-store"
  });

  // arrayBuffer(): バイナリデータとして取得
  const buffer = await res.arrayBuffer();
  // Uint8Array: protobufのデコード用に準備
  const uint8Array = new Uint8Array(buffer);

  const taskResponse = proto.TaskResponse.decode(uint8Array);
  console.timeEnd("Proto の fetch 時間");
  return taskResponse.tasks;
};

export async function fetchBooks() {
  console.time("JSON の fetch 時間");
  const res = await fetch("http://localhost:3001/books", {
    cache: "no-store"
  });
  const data = await res.json();
  console.timeEnd("JSON の fetch 時間");
  return data;
};
```


## 結果
![S__128851993.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3949612/e58e0214-5d20-4c90-9a64-112c32b5f1b0.jpeg)
　
送信データがオブジェクトで量も少なかったので、通信速度はあまり変わりませんでしたが、通信容量は格段に小さくなりました。

 今後のアプリ制作においては、UX やパフォーマンスを意識して作成していきたいです！