0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Protocol Buffersを使った所感

Posted at

先日参加したインターンシップで 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でレスを返すコントローラーを定義

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でレスを返すコントローラーを定義

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 を記述

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 を叩く関数 を定義

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
 
送信データがオブジェクトで量も少なかったので、通信速度はあまり変わりませんでしたが、通信容量は格段に小さくなりました。

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

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?