多くのクラウド アプリケーションでは、非同期メッセージを使用してシステムのコンポーネント間で情報を交換します。 メッセージングの重要な側面は、ペイロード データのエンコードに使用される形式です。 メッセージング テクノロジを選択したら、次の手順として、メッセージのエンコード方法を定義します。 使用可能なオプションは多数ありますが、適切な選択肢はユース ケースによって異なります。
この記事では、いくつかの考慮事項について説明します。
メッセージ交換のニーズ
プロデューサーとコンシューマーの間のメッセージ交換には、次の情報が必要です。
- メッセージのペイロードを定義する図形または構造体。
- ペイロードを表すエンコード形式。
- エンコードされたペイロードの読み取りと書き込みを行うシリアル化ライブラリ。
メッセージのプロデューサーは、ビジネス ロジックとコンシューマーに送信する情報に基づいてメッセージの形状を定義します。 図形を構成するには、情報を個別または関連する対象 (またはフィールド) に分割 します。 これらのフィールドの値の特性を決定します。 次の質問について考えてみましょう。
- 最も効率的なデータ型は何ですか?
- ペイロードには常に特定のフィールドがありますか?
- ペイロードには、1 つのレコードまたは値の繰り返しセットがありますか?
次に、ニーズに応じてエンコード形式を選択します。 具体的な要因としては、必要に応じて高度に構造化されたデータを作成する機能、メッセージのエンコードと転送にかかる時間、ペイロードを解析する機能などがあります。 次に、ニーズに合ったエンコード形式を選択します。
コンシューマーは、受信メッセージを正しく読み取るために、これらの決定を理解する必要があります。
メッセージを転送するために、プロデューサーはメッセージをエンコード形式にシリアル化します。 受信側では、コンシューマーはペイロードを逆シリアル化してデータにアクセスします。 このプロセスにより、両方のエンティティが同じモデルを共有できます。 図形が変更されていない限り、メッセージングは問題なく続行されます。 コントラクトが変更されると、エンコード形式はコンシューマーを中断することなく変更を処理できる必要があります。
JSON などの一部のエンコード形式は自己記述型であるため、スキーマを参照せずに解析できます。 ただし、これらの形式では、多くの場合、より大きなメッセージが生成されます。 他の形式では、データを簡単に解析できない場合がありますが、メッセージがよりコンパクトになります。 この記事では、適切な形式を選択するのに役立つ主な要因について説明します。
エンコード形式に関する考慮事項
エンコード形式は、構造化データのセットをバイトとして表す方法を定義します。 メッセージの種類は、形式の選択に影響を与える可能性があります。 ビジネス トランザクションに関連するメッセージには、多くの場合、高度に構造化されたデータが含まれています。 また、後で監査目的で構造化データを取得することもできます。 イベントのストリームの場合は、一連のレコードをできるだけ早く読み取り、統計分析のために格納することができます。
エンコード形式を選択する場合は、次の要因を考慮してください。
人間の読みやすさ
メッセージエンコードは、テキストベースとバイナリ形式に大きく分けることができます。
テキスト ベースのエンコードでは、メッセージ ペイロードはプレーン テキストであるため、ユーザーはコード ライブラリを使用せずに検査できます。 この方法により、データの読み取りと理解が容易になります。 人間が判読できる形式は、アーカイブ データに適しています。 人間はペイロードを読み取ることができるため、テキストベースの形式はデバッグが容易になり、エラーのトラブルシューティングのためにログに送信できます。
テキストベースのエンコードの欠点は、ペイロードが大きくなる傾向があるということです。 ペイロードのサイズは、必要に応じて人間の読みやすさのために元に戻すことができる限り、縮小プロセスによって減らすことができます。 一般的なテキストベースの形式は、JSON と YAML です。
暗号化
メッセージに機密データがある場合は、それらのメッセージ 全体を暗号化する必要があるかどうかを検討してください。 または、特定のフィールドのみを暗号化する必要があり、クラウド コストを削減したい場合は、 NServiceBus などのライブラリの使用を検討してください。
エンコード サイズ
メッセージ サイズは、ネットワーク全体の入力/出力のパフォーマンスに影響します。 バイナリ形式は、テキストベースの形式よりもコンパクトです。 バイナリ形式には、シリアル化と逆シリアル化のライブラリが必要です。 ペイロードは、デコードされたときにのみ読み取ることができます。
ワイヤ フットプリントを削減し、メッセージをより迅速に転送する場合は、バイナリ形式を使用します。 ストレージまたはネットワーク帯域幅が問題になるシナリオでは、このカテゴリの形式をお勧めします。 バイナリ形式のオプションには、Apache Avro、Google Protocol Buffers (protobuf)、MessagePack、および簡潔なバイナリ オブジェクト表現 (CBOR) があります。 これらの形式の長所と短所については、「エンコード形式の 選択肢」で後述します。
バイナリ形式の欠点は、ペイロードが人間が判読できないことです。 ほとんどのバイナリ形式では、保守にコストがかかる複雑なシステムが使用されます。 また、デコードするには特殊なライブラリが必要です。アーカイブ データを取得する場合はサポートされない場合があります。
非バイナリ形式の場合、縮小プロセスでは、形式の仕様に準拠したまま、不要なスペースと文字が削除されます。 この方法は、構造を変更せずにエンコード サイズを減らすのに役立ちます。 エンコーダーの機能を評価して、縮小を既定にします。 たとえば、.NET の JsonSerializerOptions.WriteIndented
の System.Text.Json.JsonSerializer
は、JSON テキストを作成する際の自動圧縮を制御します。
ペイロードの理解
メッセージペイロードはバイト列として到達します。 このシーケンスを解析するには、コンシューマーがペイロード内のデータ フィールドを記述するメタデータにアクセスできる必要があります。 メタデータを格納および配布するための 2 つの主な方法は次のとおりです。
タグ付けされたメタデータ。 一部のエンコード形式 (特に JSON) では、フィールドはメッセージの本文内でデータ型と識別子でタグ付けされます。 これらの形式は、スキーマを参照せずに値のディクショナリに解析できるため、 自己記述 型です。 コンシューマーがフィールドを理解する 1 つの方法は、期待される値を照会することです。 たとえば、プロデューサーは JSON でペイロードを送信します。 コンシューマーは JSON をディクショナリに解析し、フィールドの存在を確認してペイロードを理解します。 もう 1 つの方法は、コンシューマーがプロデューサーが共有するデータ モデルを適用することです。 たとえば、静的に型指定された言語を使用する場合、多くの JSON シリアル化ライブラリでは、JSON 文字列を型指定されたクラスに解析できます。
スキーマ。 スキーマは、メッセージの構造とデータ フィールドを正式に定義します。 このモデルでは、プロデューサーとコンシューマーは、明確に定義されたスキーマを介してコントラクトを持ちます。 スキーマでは、データ型、必須または省略可能なフィールド、バージョン情報、ペイロードの構造を定義できます。 プロデューサーは、ライター スキーマに従ってペイロードを送信します。 コンシューマーは、リーダー スキーマを適用してペイロードを受け取ります。 メッセージは、エンコード固有のライブラリを使用してシリアル化および逆シリアル化されます。 スキーマは、次の 2 つの方法で分散できます。
スキーマをプリアンブルまたはヘッダーとしてメッセージに格納しますが、ペイロードとは別に格納します。
スキーマを外部に格納します。
一部のエンコード形式ではスキーマが定義され、スキーマからクラスを生成するツールが使用されます。 プロデューサーとコンシューマーは、これらのクラスとライブラリを使用してペイロードをシリアル化および逆シリアル化します。 ライブラリは、ライタースキーマとリーダースキーマの間の互換性チェックも提供します。 protobuf と Apache Avro の両方がそのアプローチに従います。 主な違いは、protobuf には言語に依存しないスキーマ定義があり、Avro ではコンパクトな JSON が使用されていることです。 もう 1 つの違いは、両方の形式がリーダー スキーマとライター スキーマ間の互換性チェックを提供する方法です。
スキーマを外部に格納するもう 1 つの方法は、スキーマ レジストリにあります。 メッセージには、スキーマとペイロードへの参照が含まれています。 プロデューサーは、メッセージ内のスキーマ識別子を送信します。 コンシューマーは、外部ストアからその識別子を指定してスキーマを取得します。 どちらの当事者も、メッセージの読み取りと書き込みに形式固有のライブラリを使用します。 スキーマの格納に加えて、レジストリは互換性チェックを提供して、スキーマの進化に伴ってプロデューサーとコンシューマーの間のコントラクトが破損しないようにすることができます。
方法を選択する前に、転送データのサイズか、後でアーカイブされたデータを解析する機能がより重要であるかを決定します。
ペイロードと共にスキーマを格納すると、エンコード サイズが大きくなり、断続的なメッセージに最適です。 バイトの小さなチャンクを転送することが重要であるか、レコードのシーケンスが必要な場合は、この方法を選択します。 外部スキーマ ストアを維持するためのコストは高くなる可能性があります。
ただし、ペイロードのオンデマンド デコードがサイズよりも重要な場合は、ペイロードを含むスキーマやタグ付けされたメタデータ アプローチを含め、後でデコードすることが保証されます。 メッセージ サイズが大幅に増加し、ストレージのコストに影響を与える可能性があります。
スキーマのバージョン管理
ビジネス要件が変わると、形状が変わると予想され、スキーマが進化します。 バージョン管理を使用すると、プロデューサーは新しい機能を含む可能性のあるスキーマの更新を示すことができます。 バージョン管理には、次の 2 つの重要な側面があります。
コンシューマーは、変更を追跡して理解する必要があります。
1 つの方法は、コンシューマーがすべてのフィールドをチェックして、スキーマが変更されたかどうかを判断することです。 もう 1 つの方法は、プロデューサーがメッセージを含むスキーマ バージョン番号を発行することです。 スキーマが進化すると、プロデューサーはバージョンをインクリメントします。
変更は、コンシューマーのビジネス ロジックに影響を与えたり、中断したりしてはなりません。
フィールドが既存のスキーマに追加されるとします。 新しいバージョンを使用するコンシューマーが古いバージョンに従ってペイロードを取得した場合、新しいフィールドの不足を見過ごせないと、ロジックが壊れる可能性があります。 次に、逆のシナリオを検討します。 新しいスキーマでフィールドが削除された場合、古いスキーマを使用するコンシューマーはデータを読み取ることができない可能性があります。
Avro などのエンコード形式は、既定値を定義する機能を提供します。 前の例では、フィールドが既定値で追加されると、不足しているフィールドに既定値が設定されます。 protobuf などの他の形式では、必須フィールドと省略可能なフィールドを通じて同様の機能が提供されます。
ペイロードの構造
ペイロード内のデータがレコードのシーケンスとして、または単一の個別のペイロードとして構造化されているかどうかを検討します。 ペイロード構造は、次のいずれかのモデルに分類できます。
配列/ディクショナリ/値: 1 つの配列または多次元配列の値を保持するエントリを定義します。 エントリには一意のキーと値のペアがあります。 モデルは、複雑な構造を表すために拡張できます。 たとえば、JSON、Apache Avro、MessagePack などがあります。
このレイアウトは、メッセージが異なるスキーマで個別にエンコードされている場合に適しています。 複数のレコードがある場合、ペイロードは過剰に冗長になる可能性があります。 この冗長性により、ペイロードが肥大化する可能性があります。
表形式データ: 情報は行と列に分割されます。 各列はフィールドまたは情報の件名を示し、各行にはそれらのフィールドの値が含まれます。 このレイアウトは、時系列データなどの情報の繰り返しセットに対して効率的です。
Comma-Separated 値 (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 はオープン標準であり、その形式は RFC 8259 のインターネット エンジニアリング タスク フォース (IETF) によって定義されています。 JSON は、配列/ディクショナリ/値モデルに従うテキストベースの形式です。
JSON はメタデータのタグ付けに使用でき、スキーマなしでペイロードを解析できます。 JSON では、省略可能なフィールドを指定するオプションがサポートされています。これは、前方互換性と下位互換性の両方に役立ちます。
最大の利点は、それが普遍的に利用可能であるということです。 JSON は、最も相互運用可能なエンコード形式であり、多くのメッセージング サービスの既定値です。
JSON はテキストベースの形式であるため、ネットワーク経由では効率的ではなく、ストレージが問題になる場合には理想的ではありません。 可能な場合は縮小手法を使用します。 キャッシュされた項目を HTTP 経由でクライアントに直接返す場合、JSON を格納すると、別の形式から逆シリアル化してから JSON にシリアル化するコストが節約される可能性があります。
単一レコード メッセージ、または各メッセージが異なるスキーマを持つ一連のメッセージには JSON を使用します。 時系列データなど、一連のレコードには JSON を使用しないでください。
バイナリ JSON (BSON) など、JSON には他にもバリエーションがあります。 BSON は、MongoDB で動作するように配置されたバイナリ エンコードです。
CSV
CSV は、テキストベースの表形式です。 テーブルのヘッダーはフィールドを示します。 CSV は、一連のレコードを含むメッセージに適しています。
CSV の欠点は、標準化の欠如です。 区切り記号、ヘッダー、および空のフィールドを表す方法は複数あります。
プロトコル バッファー
プロトコル バッファー (または protobuf) は、厳密に型指定された定義ファイルを使用してキーと値のペアでスキーマを定義するシリアル化形式です。 これらの定義ファイルは、メッセージのシリアル化と逆シリアル化に使用される言語固有のクラスにコンパイルされます。
メッセージには、圧縮された小さなバイナリ ペイロードが含まれています。その結果、データ転送が高速化されます。 欠点は、ペイロードが人間が判読できないことです。 また、スキーマは外部に格納されるため、この形式はアーカイブされたデータを取得する必要があるシナリオには適していません。
Apache Avro
Apache Avro は、protobuf に似た定義ファイルを使用するバイナリ シリアル化形式ですが、コンパイル手順はありません。 代わりに、シリアル化されたデータには常にスキーマ プリアンブルが含まれます。
プリアンブルには、ヘッダーまたはスキーマ識別子を含めることができます。 エンコード サイズが小さいため、Avro はデータのストリーミングに推奨されます。 また、一連のレコードに適用されるヘッダーがあるため、表形式データに適しています。
Apache Parquet
Apache Parquet は、通常、Apache Hadoop および関連するデータ処理フレームワークに関連付けられている列形式のストレージ ファイル形式です。
Apache Parquet はデータ圧縮をサポートしており、スキーマの進化に対する機能は限られています。 通常、この形式は、ワークロード内の他のビッグ データ テクノロジでデータの作成または使用に必要な場合に使用されます。
MessagePack (メッセージパック)
MessagePack は、ワイヤ経由の伝送用にコンパクトに設計されたバイナリ シリアル化形式です。 MessagePack にスキーマ定義と型チェックがありません。 この形式は、一括ストレージには推奨されません。
CBOR
CBOR (仕様) は、小さなエンコード サイズを提供するバイナリ形式です。 MessagePack よりも CBOR を使用する利点は、RFC7049での IETF への準拠です。