Bond

シリアライゼーションC++C#Pythonバイナリフォーマットスキーマ駆動

ライブラリ

Bond - Microsoftのクロスプラットフォームシリアライゼーションフレームワーク

概要

Bondは、Microsoftが開発したスキーマ駆動型のシリアライゼーションフレームワークです。高性能なバイナリ形式、JSONなどの複数のプロトコルをサポートし、C++、C#、Pythonなど複数の言語で利用できます。Protocol Buffersに似た概念ですが、より柔軟な型システムと豊富なカスタマイズオプションを提供します。特に大規模なシステムやマイクロサービス間の通信で威力を発揮します。

詳細

Bondは、Microsoft内部で大規模サービスのデータ交換に使用されてきた実績を持つシリアライゼーションライブラリです。スキーマ定義言語(IDL)を使用して、型安全でバージョン互換性のあるデータ構造を定義します。コンパイル時にスキーマから各言語用のコードを生成し、効率的なシリアライゼーション/デシリアライゼーションを実現します。

Bondの特徴的な機能として、複数のプロトコルサポートがあります。高速なバイナリ形式(Compact Binary、Fast Binary)、人間が読めるJSON形式、XMLなど、用途に応じて選択できます。また、スキーマの進化に対応した優れた互換性メカニズムを持ち、フィールドの追加・削除・型変更を安全に行えます。

ジェネリクスのサポートも特徴的で、C++のテンプレートやC#のジェネリクスと自然に統合されます。また、ストリーミングAPIを提供し、大きなデータセットの効率的な処理が可能です。カスタム型変換やアトリビュートによる振る舞いのカスタマイズも柔軟に行えます。

メリット・デメリット

メリット

  • 高性能: 最適化されたバイナリフォーマットで高速なシリアライゼーション
  • 言語横断: C++、C#、Python、Javaなど複数言語をサポート
  • スキーマ進化: 後方互換性を保ちながらスキーマを変更可能
  • 複数プロトコル: バイナリ、JSON、XMLなど用途に応じて選択可能
  • 型安全: コンパイル時の型チェックでバグを早期発見
  • カスタマイズ性: 豊富な拡張ポイントとカスタマイズオプション

デメリット

  • 学習曲線: スキーマ定義言語とコード生成プロセスの理解が必要
  • ビルド複雑性: スキーマコンパイラの統合がビルドプロセスを複雑化
  • コミュニティ: Protocol Buffersと比較して小規模なコミュニティ
  • ドキュメント: 公式ドキュメントが限定的で事例が少ない
  • 依存関係: コード生成ツールへの依存が避けられない
  • デバッグ: 生成されたコードのデバッグが困難な場合がある

参考ページ

書き方の例

1. スキーマ定義(.bond ファイル)

// person.bond
namespace Examples

struct Address
{
    0: string street;
    1: string city;
    2: string zipCode;
}

struct Person
{
    0: string name;
    1: uint32 age;
    2: optional<string> email;
    3: vector<Address> addresses;
    4: map<string, string> metadata;
}

struct Employee : Person
{
    10: string employeeId;
    11: double salary;
    12: nullable<Person> manager;
}

2. C#でのシリアライゼーション

using Bond;
using Bond.Protocols;
using Bond.IO.Unsafe;
using System.IO;

// 生成されたクラスを使用
var employee = new Employee
{
    name = "田中太郎",
    age = 30,
    email = "[email protected]",
    addresses = new List<Address>
    {
        new Address 
        { 
            street = "千代田区丸の内1-1-1",
            city = "東京都",
            zipCode = "100-0001"
        }
    },
    employeeId = "EMP001",
    salary = 500000.0
};

// Compact Binary形式でシリアライズ
var output = new OutputBuffer();
var writer = new CompactBinaryWriter<OutputBuffer>(output);
Serialize.To(writer, employee);
byte[] data = output.Data.Array;

// デシリアライズ
var input = new InputBuffer(data);
var reader = new CompactBinaryReader<InputBuffer>(input);
var deserialized = Deserialize<Employee>.From(reader);

Console.WriteLine($"Name: {deserialized.name}, ID: {deserialized.employeeId}");

3. JSON形式でのシリアライゼーション

using Bond.Protocols;
using Newtonsoft.Json;

class JsonExample
{
    static void UseJsonProtocol()
    {
        var person = new Person
        {
            name = "山田花子",
            age = 25,
            email = "[email protected]",
            metadata = new Dictionary<string, string>
            {
                ["department"] = "営業部",
                ["location"] = "大阪"
            }
        };

        // JSON形式でシリアライズ
        var serializer = new SimpleJsonSerializer();
        string json = serializer.Serialize(person);
        Console.WriteLine("JSON: " + json);

        // JSONからデシリアライズ
        var deserializer = new SimpleJsonDeserializer();
        var deserialized = deserializer.Deserialize<Person>(json);
        
        Console.WriteLine($"Name: {deserialized.name}, Age: {deserialized.age}");
    }
}

4. C++でのストリーミング処理

#include <bond/core/bond.h>
#include <bond/protocol/compact_binary.h>
#include <bond/stream/output_buffer.h>
#include "person_reflection.h"

void StreamingExample()
{
    // 大量のPersonオブジェクトをストリーミング
    bond::OutputBuffer output;
    bond::CompactBinaryWriter<bond::OutputBuffer> writer(output);

    // 複数のオブジェクトを連続して書き込み
    for (int i = 0; i < 10000; ++i)
    {
        Examples::Person person;
        person.name = "User" + std::to_string(i);
        person.age = 20 + (i % 50);
        
        bond::Serialize(person, writer);
    }

    // ストリーミング読み込み
    bond::InputBuffer input(output.GetBuffer());
    bond::CompactBinaryReader<bond::InputBuffer> reader(input);

    while (!reader.IsEof())
    {
        Examples::Person person;
        bond::Deserialize(reader, person);
        
        // 各オブジェクトを処理
        ProcessPerson(person);
    }
}

5. スキーマ進化の例

// version 1
struct Product
{
    0: string name;
    1: double price;
}

// version 2 - フィールド追加(後方互換性あり)
struct Product
{
    0: string name;
    1: double price;
    2: optional<string> category = "general";  // デフォルト値付き
    3: vector<string> tags;  // 新規フィールド
}
// 古いバージョンのデータを新しいスキーマで読み込み
class SchemaEvolution
{
    static void HandleVersioning()
    {
        // v1形式でシリアライズされたデータ
        byte[] oldData = GetOldVersionData();
        
        // v2スキーマでデシリアライズ
        var input = new InputBuffer(oldData);
        var reader = new CompactBinaryReader<InputBuffer>(input);
        var product = Deserialize<Product>.From(reader);
        
        // 新しいフィールドはデフォルト値または空
        Console.WriteLine($"Category: {product.category ?? "general"}");
        Console.WriteLine($"Tags count: {product.tags?.Count ?? 0}");
    }
}

6. カスタム型変換とアトリビュート

// カスタムアトリビュート付きスキーマ
struct TimeSeries
{
    [JsonName("timestamp")]
    0: int64 unixTimestamp;
    
    [Bond.Id(1), Bond.Type(typeof(double))]
    1: float value;
    
    [Bond.Required]
    2: string sensorId;
}
// カスタム変換の実装
class CustomTransform : ITransform
{
    public void Begin(ITransformContext context) { }
    public void End(ITransformContext context) { }
    
    public bool OnField(ITransformContext context, 
                       FieldInfo field, 
                       object value)
    {
        // タイムスタンプをDateTimeに変換
        if (field.Name == "unixTimestamp" && value is long timestamp)
        {
            var dateTime = DateTimeOffset.FromUnixTimeSeconds(timestamp);
            Console.WriteLine($"Timestamp: {dateTime}");
        }
        
        return true;
    }
}

// 使用例
var timeSeries = new TimeSeries
{
    unixTimestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
    value = 23.5f,
    sensorId = "SENSOR-001"
};

// カスタム変換を適用
var transform = new CustomTransform();
var transcoder = new Transcoder<CompactBinaryWriter<OutputBuffer>>(transform);
transcoder.Transcode(timeSeries);