Protobuf メッセージ構造の効率的な設計手法とリピーテッドフィールドの活用

Protobuf は、JSON と比較して高い処理効率を持つデータ交換フォーマットとして広く利用されています。今回のテーマでは、単純なデータ変換を超えて、実際の開発現場で必要となる複雑なデータ構造の扱い方について解説します。具体的には、コードの再利用性を高めるためのメッセージ定義と、リスト形式のデータを取り扱う方法について述べます。

メッセージ定義の共有と再利用

システム開発中、複数の API 応答メッセージで共通するエラーコードや詳細説明を記述する場合、個別に定義を行うとメンテナンスコストが増大します。このような重複を回避するため、共通フィールドを含む新しいメッセージタイプを定義し、既存のレスポンスから参照するアプローチが有効です。

例えば、一般的なステータス情報を表す StatusInfo メッセージを作成し、各エンドポイントのレスポンス内部に埋め込む形にします。C++ でこれらを使用する場合、ネストされたフィールドへアクセスするには、自動生成された mutable_名前 メソッドを利用します。これにより、子オブジェクトへのポインタを取得し、直接値を設定することが可能です。

#include <iostream>
#include "api_service.pb.h"

void configure_response() {
    api::ApiResponse reply;
    // サブメッセージへのポインタを取得
    api::StatusInfo* status_obj = reply.mutable_status();
    
    // 値を設定
    status_obj->set_code(200);
    status_obj->set_description("正常に完了");
}

配列データの取り扱い(Repetition)

単一のオブジェクトではなく、コレクション形式のデータを返却する場合、Protobuf では repeated 指定子をフィールド宣言に使用します。これにより、同じ型のメッセージを複数格納できる配列構造が構築されます。加えて、列挙体(Enum)を利用して、特定のセットとなった整数値を定義することもサポートされています。

以下に、ユーザプロフィール情報とそれらをまとめて返却するレスポンス定義の例を示します。ここでは、ステータス定義を共有しつつ、ユーザーリストを扱えるように設計しています。

syntax = "proto3";
package app_data;

// 汎用的な状態管理定義
message StatusInfo {
    int32 code = 1;
    string description = 2;
}

// ユーザー情報メッセージ
message UserProfile {
    bytes username = 1;
    uint32 age = 2;
    enum Gender {
        UNKNOWN = 0;
        MALE = 1;
        FEMALE = 2;
    }
    Gender sex = 3;
}

// リクエストメッセージ
message GetContactsRequest {
    int32 owner_id = 1;
}

// レスポンスメッセージ
message GetContactsResponse {
    StatusInfo status_info = 1;
    // repeated を使用して複数の UserProfile を保持
    repeated UserProfile contacts = 2;
}

生成されたクラスファイルを実行環境で使用する際、コンパイラからの指示に従ってビルドを行ってください。リストフィールドへのアイテム追加については、生成された add_リスト名 メソッドを使用して動的に行います。これは、シリアライズ前の段階で構造を構築する際によく使われるパターンです。

int execute_test() {
    GetContactsResponse response;
    
    // 状態情報の初期化
    auto* stat = response.mutable_status_info();
    stat->set_code(0);
    stat->set_description("エラーなし");
    
    // リストへの要素追加
    auto* user_a = response.add_contacts();
    user_a->set_username("Tom");
    user_a->set_age(24);
    user_a->set_sexy(Gender::MALE);
    
    auto* user_b = response.add_contacts();
    user_b->set_username("Jerry");
    user_b->set_age(28);
    user_b->set_sexy(Gender::FEMALE);
    
    // インスタンス内のサイズ確認
    std::cout << response.contacts_size() << std::endl;
    return 0;
}

タグ: protobuf cpp RPC Serialization data-encoding

7月7日 19:46 投稿