メッセージ エンコードに関する考慮事項

多くのクラウド アプリケーションでは、システムのコンポーネント間で情報を交換するために、非同期メッセージが使用されています。 メッセージングの 1 つの重要な側面は、ペイロード データのエンコードに使用される形式です。 メッセージング テクノロジを選択したら、次の手順は、メッセージをエンコードする方法を定義することです。 使用できるオプションは多数ありますが、適切な選択はご自身のユース ケースによって異なります。

この記事では、いくつかの考慮事項について説明します。

メッセージ交換のニーズ

プロデューサーとコンシューマー間のメッセージ交換には、次のものが必要です。

  • メッセージのペイロードを定義する形状または構造。
  • ペイロードを表すためのエンコード形式。
  • エンコードされたペイロードの読み取りと書き込みを行うためのシリアル化ライブラリ。

メッセージのプロデューサーは、ビジネス ロジックおよびコンシューマーに送信する情報に基づいて、メッセージの形状を定義します。 この形状を構成するために、情報を不連続な、または関連するサブジェクト (フィールド) に分割します。 これらのフィールドの値の特性を決定します。 最も効率的なデータ型は何であるか、考慮しましょう。 ペイロードには常に特定のフィールドが含まれますか。 ペイロードには 1 つのレコードが含まれますか、または繰り返される値のセットが含まれますか。

次に、ニーズに合わせてエンコード形式を選択します。 特定の要因としては、必要に応じて高度な構造化データを作成する機能、メッセージのエンコードと転送にかかる時間、およびペイロードを解析する機能などがあります。 エンコード形式に応じて、適切にサポートされているシリアル化ライブラリを選択します。

メッセージのコンシューマーは、受信メッセージの読み取り方法を把握できるように、これらの決定に注意する必要があります。

メッセージを転送するために、プロデューサーはメッセージをエンコード形式にシリアル化します。 受信側のコンシューマーは、データを使用するためにペイロードを逆シリアル化します。 これにより、両方のエンティティがモデルを共有し、その形状が変更されない限り、メッセージングは問題なく続行されます。 コントラクトが変更された場合、エンコード形式では、コンシューマーを中断させることなくその変更を処理できる必要があります。

JSON などの一部のエンコード形式は自己記述型であるため、スキーマを参照せずに解析することができます。 ただし、このような形式を使用すると、サイズの大きいメッセージが生成される傾向があります。 他の形式では、データの解析が同じように簡単ではない可能性がありますが、メッセージはコンパクトになります。 この記事では、形式を選択する際に役立ついくつかの要素について説明します。

エンコード形式に関する考慮事項

エンコード形式により、一連の構造化データをバイトとして表現する方法が定義されます。 メッセージの種類は、形式の選択に影響を与える可能性があります。 ビジネス トランザクションに関連するメッセージには、高度な構造化データが含まれる可能性が最も高くなります。 また、後で監査のためにこれを取得する必要があるかもしれません。 イベントのストリームについては、可能な限りすばやく一連のレコードを読み取り、それを統計分析のために格納することをお勧めします。

ここでは、エンコード形式を選択する際に考慮する必要があるいくつかの点について説明します。

人間の読みやすさ

メッセージのエンコードは、テキスト ベースの形式とバイナリ形式に広く分けることができます。

テキスト ベースのエンコードでは、メッセージ ペイロードがプレーン テキストであるため、コード ライブラリを使用せずに人が調べることができます。 人間が判読できる形式は、アーカイブ データに適しています。 また、人間がペイロードを判読できるため、テキスト ベースの形式はより簡単にデバッグでき、エラーをトラブルシューティングするためにログに送信することができます。

欠点は、ペイロードが大きくなる傾向があることです。 一般的なテキスト ベースの形式は JSON です。

暗号化

メッセージに機密データがある場合は、Azure Service Bus の保存データの暗号化に関するこちらのガイダンスで説明されているように、それらのメッセージ全体を暗号化する必要があるかどうかを検討します。 または、特定のフィールドのみを暗号化する必要があり、クラウド コストを削減したい場合は、そのために NServiceBus などのライブラリを使用することを検討します。

エンコードのサイズ

メッセージのサイズは、ネットワーク上のネットワーク I/O パフォーマンスに影響を与えます。 バイナリ形式は、テキスト ベースの形式よりもコンパクトです。 バイナリ形式では、シリアル化、逆シリアル化のライブラリが必要です。 ペイロードは、デコードしない限り読み取ることができません。

ネットワーク占有領域を削減し、メッセージをより速く転送する必要がある場合は、バイナリ形式を使用します。 このカテゴリの形式は、ストレージやネットワーク帯域幅が問題になるシナリオで推奨されます。 バイナリ形式のオプションには、Apache Avro、Google Protocol Buffers (protobuf)、MessagePack、および Concise Binary Object Representation (CBOR) があります。 これらの形式の長所と短所については、こちらのセクションで説明します。

欠点は、人間がそのペイロードを判読できないことです。 ほとんどのバイナリ形式では、保守にコストがかかる場合がある複雑なシステムが使用されます。 また、それらをデコードするには特殊なライブラリが必要です。これは、アーカイブ データを取得する場合はサポートされていない可能性があります。

ペイロードについて

メッセージ ペイロードは、バイトのシーケンスとして受信されます。 このシーケンスを解析するために、コンシューマーは、ペイロード内のデータ フィールドについて記述するメタデータにアクセスできる必要があります。 メタデータの格納と配布を行うには、主に次の 2 つの方法があります。

タグ付きメタデータ。 一部のエンコード (特に JSON) では、メッセージの本文内で、フィールドにデータ型と識別子がタグ付けされます。 これらの形式は、スキーマを参照せずに値のディクショナリに解析できるため、"自己記述型" です。 コンシューマーがフィールドを理解する方法の 1 つは、予期される値を照会することです。 たとえば、プロデューサーが JSON でペイロードを送信したとします。 コンシューマーは JSON をディクショナリに解析し、ペイロードを理解するためのフィールドの存在を確認します。 もう 1 つの方法は、コンシューマーが、プロデューサーによって共有されたデータ モデルを適用することです。 たとえば、静的に型指定された言語を使用する場合、多くの JSON シリアル化ライブラリでは JSON 文字列を型指定されたクラスに解析することができます。

スキーマ。 スキーマでは、メッセージの構造とデータ フィールドを形式的に定義します。 このモデルでは、プロデューサーとコンシューマーが、適切に定義されたスキーマを通じてコントラクトを持ちます。 スキーマでは、データ型、必須またはオプションのフィールド、バージョン情報、およびペイロードの構造を定義できます。 プロデューサーは、ライター スキーマに従ってペイロードを送信します。 コンシューマーは、リーダー スキーマを適用することによってペイロードを受信します。 メッセージは、エンコード固有のライブラリを使用することでシリアル化または逆シリアル化されます。 スキーマを配布するには、次の 2 つの方法があります。

  • スキーマを、メッセージ内のプリアンブルまたはヘッダーとして、ペイロードとは別に格納する。

  • スキーマを外部に格納する。

一部のエンコード形式では、スキーマを定義し、そのスキーマからクラスを生成するツールを使用します。 プロデューサーとコンシューマーは、これらのクラスとライブラリを使用して、ペイロードのシリアル化および逆シリアル化を行います。 また、このライブラリにより、ライター スキーマとリーダー スキーマ間の互換性チェックも提供されます。 protobuf と Apache Avro の両方でこのアプローチが使用されています。 主な違いは、protobuf には言語に依存しないスキーマ定義がありますが、Avro ではコンパクトな JSON が使用される点です。 もう 1 つの違いは、両方の形式におけるリーダー スキーマとライター スキーマ間の互換性チェックを提供する方法にあります。

もう 1 つの方法は、外部のスキーマ レジストリにスキーマを格納することです。 メッセージには、スキーマへの参照とペイロードが含まれます。 プロデューサーは、メッセージでスキーマ識別子を送信し、コンシューマーは、その識別子を指定することによって外部ストアからスキーマを取得します。 どちらのパーティーでも、形式固有のライブラリを使用してメッセージの読み取りと書き込みを行います。 格納する機能とは別に、スキーマ レジストリには、スキーマの進化に伴ってプロデューサーとコンシューマー間のコントラクトが破損していないことを確認するための互換性チェックが用意されています。

方法を選択する前に、転送データのサイズか、アーカイブされたデータを後で解析する機能か、どちらがより重要であるかを判断してください。

スキーマをペイロードと共に格納する場合、エンコード サイズがより大きくなるため、間欠的なメッセージに適しています。 小さなバイト チャンクを転送することが重要な場合や、レコードのシーケンスを想定している場合は、この方法を選択します。 外部スキーマ ストアを維持するコストは高くなる可能性があります。

ただし、ペイロードをオンデマンドでデコードすることがサイズよりも重要である場合は、ペイロードと共にスキーマを含める方法、またはタグ付きメタデータを使用する方法によって、後からのデコードが保証されます。 メッセージ サイズが大幅に増加する可能性があり、ストレージのコストに影響を与える可能性があります。

スキーマのバージョン管理

ビジネス要件が変化すると、形状が変化することが予想され、スキーマが進化します。 プロデューサーは、バージョン管理を使用して、新機能を含む可能性のあるスキーマの更新を示すことができます。 バージョン管理には次の 2 つの側面があります。

  • コンシューマーが変更を認識している必要があります。

    1 つの方法は、コンシューマーがすべてのフィールドをチェックして、スキーマが変更されたかどうかを確認することです。 もう 1 つの方法は、プロデューサーが、メッセージを使用してスキーマのバージョン番号を発行することです。 スキーマが進化したら、プロデューサーはバージョンをインクリメントします。

  • 変更によってコンシューマーのビジネス ロジックが影響を受けたり、中断されたりしてはなりません。

    既存のスキーマにフィールドが追加されたとします。 新しいバージョンを使用しているコンシューマーが古いバージョンに従ってペイロードを取得した場合、新しいフィールドの不足を見過ごすことができないと、コンシューマーのロジックが中断される可能性があります。 反対のケースとして、新しいスキーマでフィールドが削除されたとします。 古いスキーマを使用しているコンシューマーは、データを読み取ることができない可能性があります。

    Avro などのエンコード形式では、既定値を定義する機能が提供されています。 前の例では、既定値と共にフィールドが追加された場合、不足しているフィールドには既定値が設定されます。 protobuf などの他の形式でも、必須フィールドとオプション フィールドを通じて同様の機能が提供されています。

ペイロードの構造

ペイロード内でデータが配置される方法を考えてみましょう。 それはレコードのシーケンスでしょうか、または不連続な単一のペイロードでしょうか。 ペイロードの構造は、次のいずれかのモデルに分類できます。

  • Array/dictionary/value: 1 次元配列または多次元配列に値を保持するエントリを定義します。 エントリには、一意のキーと値のペアが含まれます。 これを拡張して、複雑な構造を表すことができます。 例としては、JSON、Apache Avro、MessagePack などがあります。

    このレイアウトは、メッセージが異なるスキーマを使用して個別にエンコードされている場合に適しています。 複数のレコードがある場合は、ペイロードが過剰に冗長になり、ペイロードの肥大化が発生する可能性があります。

  • 表形式データ: 情報は行と列に分割されます。 各列は、フィールド、または情報のサブジェクトを示し、各行にはこれらのフィールドの値が含まれます。 このレイアウトは、時系列データなどの、繰り返される情報のセットに対して効率的です。

    CSV は、最も単純なテキスト ベース形式の 1 つです。 これにより、共通ヘッダーを持つレコードのシーケンスとしてデータが提示されます。 バイナリ エンコードの場合、Apache Avro には CSV ヘッダーに似たプリアンブルがありますが、エンコード サイズはコンパクトになります。

ライブラリのサポート

独自のモデルに対して、既知の形式を使用することを検討してください。

既知の形式は、コミュニティから普遍的にサポートされているライブラリを通じてサポートされています。 特殊な形式を使用する場合は、特定のライブラリが必要です。 ビジネス ロジックでは、そのライブラリによって提供される API 設計の選択の一部を回避することが必要になる場合があります。

スキーマ ベースの形式の場合は、リーダー スキーマとライター スキーマ間の互換性チェックを行うエンコード ライブラリを選択します。 Apache Avro などの特定のエンコード ライブラリでは、メッセージを逆シリアル化する前に、コンシューマーがライター スキーマとリーダー スキーマの両方を指定することを想定しています。 このチェックにより、コンシューマーがスキーマのバージョンを認識していることが確認されます。

相互運用性

形式の選択は、特定のワークロードまたはテクノロジのエコシステムによって異なる場合があります。

次に例を示します。

  • Azure Stream Analytics では、JSON、CSV、Avro がネイティブでサポートされています。 Stream Analytics を使用する場合は、可能であればこれらの形式のいずれかを選択することをお勧めします。 そうでない場合は、カスタムの逆シリアライザーを提供することができますが、これによりソリューションの複雑さが増します。

  • JSON は、HTTP REST API 用の標準の交換形式です。 アプリケーションがクライアントから JSON ペイロードを受信し、次に非同期処理のためにそれらをメッセージ キューに配置する場合は、別の形式に再エンコードするのではなく、メッセージングに JSON を使用することが理にかなっている場合があります。

これらは、相互運用性に関する考慮事項の 2 つの例にすぎません。 一般に、標準化された形式は、カスタム形式よりも相互運用性が高まります。 テキスト ベースのオプションでは、JSON が最も相互運用性の高いものの 1 つです。

エンコード形式の選択肢

いくつかの一般的なエンコード形式を次に示します。 形式を選択する前に、これらの考慮事項について検討してください。

JSON

JSON はオープン標準 (IETF RFC8259) です。 これは、配列/ディクショナリ/値のモデルに従うテキスト ベースの形式です。

JSON はメタデータのタグ付けに使用することができ、ユーザーはスキーマなしでペイロードを解析できます。 JSON では、省略可能なフィールドを指定するオプションがサポートされています。これは上位互換性と下位互換性のために役立ちます。

最大の利点は、これを普遍的に利用できることです。 最も相互運用性が高く、多くのメッセージング サービスの既定のエンコード形式となっています。

これはテキスト ベースの形式であるため、ネットワーク経由の場合は効率的ではなく、ストレージが問題になる場合は最適な選択ではありません。 キャッシュされた項目を HTTP 経由でクライアントに直接返す場合は、JSON で保存することで、別の形式から逆シリアル化した後 JSON にシリアル化するコストを節約できます。

単一レコードのメッセージ、または各メッセージのスキーマが異なるメッセージのシーケンスに対して使用します。 時系列データなど、レコードのシーケンスに対しては JSON を使用しないようにしてください。

JSON には、BSON など、他のバリエーションも存在します。これは、MongoDB と連動するように調整されたバイナリ エンコードです。

コンマ区切り値 (CSV)

CSV は、テキスト ベースの表形式です。 表のヘッダーはフィールドを示します。 メッセージに一連のレコードが含まれている場合は、これを選択することをお勧めします。

欠点は、標準化が不足していることです。 区切り、ヘッダー、空のフィールドを表す方法が多数存在しています。

Protocol Buffers (protobuf)

Protocol Buffers (または protobuf) は、厳密に型指定された定義ファイルを使用して、キーと値のペアでスキーマを定義するシリアル化形式です。 これらの定義ファイルは、メッセージのシリアル化と逆シリアル化のために使用される言語固有のクラスにコンパイルされます。

メッセージには、圧縮されたバイナリの小さなペイロードが含まれます。その結果、より高速な転送が実現されます。 欠点は、このペイロードは人間が判読できないということです。 また、スキーマが外部であるため、アーカイブされたデータを取得する必要があるケースにはお勧めしません。

Apache Avro

Apache Avro は、protobuf に似た定義ファイルを使用するバイナリ シリアル化形式ですが、コンパイルのステップは存在しません。 代わりに、シリアル化されたデータに、必ずスキーマのプリアンブルが含まれます。

このプリアンブルには、ヘッダーまたはスキーマ識別子を保持できます。 エンコード サイズが小さいため、ストリーミング データに対しては Avro を使用することをお勧めします。 また、一連のレコードに適用されるヘッダーがあるため、表形式データに適した選択となります。

MessagePack

MessagePack は、ネットワーク経由での転送サイズを小型化するように設計されたバイナリのシリアル化形式です。 メッセージ スキーマやメッセージ型のチェックはありません。 この形式は、大容量のストレージには推奨されません。

CBOR

Concise Binary Object Representation (CBOR) (仕様) は、小さなエンコード サイズを提供するバイナリ形式です。 MessagePack に対する CBOR の利点は、RFC7049 の IETF に準拠していることです。

次のステップ