ベロシティのための設計:スキーマレスなイベント取り込みという選択
SchemaBridge Team · 2025-12-08 · Event Ingestion, JSON, DX
イベント駆動システムにおけるスピードと厳格な型のトレードオフ。私たちが厳格な型定義ではなく生のJSONを選んだ理由。
スキーマのパラドックス:味方か、敵か?
従来のエンタープライズソフトウェアでは、厳格なスキーマ(SQL、Protobuf、WSDL)が信頼性の基盤でした。契約は単純です。私がデータの形を定義し、あなたはそれに従い、コンパイラがクラッシュしないことを保証する。このアプローチは、パイプの両端を自分たちが所有する管理された環境で、何十年にもわたって役立ってきました。コンパイル時の安全性、効率的なバイナリシリアライゼーション、そして開発者にとって明確な「信頼できる唯一の情報源」をもたらします。
しかし、SaaS ベンダーが毎週のようにペイロードを変え、レガシー ERP が「柔軟な」CSV を吐き出し、社内マイクロサービスが毎月生まれては退役していく現代の高速な統合環境では、硬直したスキーマは拘束衣になります。安全性と引き換えに慣性をもたらすのです。周囲の世界が流動的なとき、硬直した契約は土台ではなく、障害点になります。スキーマのパラドックスとはこうです。厳格な型でシステムを守ろうとすればするほど、外の世界が変化したときにシステムは脆くなるのです。
歴史的な変遷:COBOL から JSON へ
スキーマレスなシステムが求められる理由を理解するには、データ交換の歴史を振り返る必要があります。メインフレームの初期、データは固定長レコード(COBOL の COPYBOOK)に格納されていました。フィールドを 1 つ追加したければ、そのレコードを読むすべてのプログラムを再コンパイルする必要がありました。究極の硬直したスキーマです。これは「単一マシン思考」の時代で、ストレージのコストが非常に高かったため、すべてのバイトが固定位置で管理されなければなりませんでした。階層の余地も、任意項目の余地も、そしてもちろん進化の余地もありませんでした。レコードの 1 文字 1 文字が貴重な資源であり、どんな変更も何週間もの計画とテストを要する大事件でした。
1980 年代から 90 年代にかけて、私たちはリレーショナルデータベースと SQL へ移行しました。構造化されたリレーションという概念をもたらした点で、これは大きな前進でした。しかし同時にデータベースマイグレーションももたらしました。カラムの追加は、ダウンタイム、テーブルロック、そして DBA と開発者の綿密な調整を意味しました。スキーマは依然として、開発者がイノベーションのたびに乗り越えなければならない壁でした。Hibernate のような ORM が普及しても、テーブル構造の根底にある硬直性が、何が可能かを決める最終的な裁定者であり続けました。
その後、90 年代後半に XML と SOAP が登場しました。「タグ」という概念により、ある程度の柔軟性が生まれました。XML タグを追加しても、必ずしもパーサーが壊れるわけではありませんでした。しかし業界はすぐに XSD(XML Schema Definition) と WSDL を追加し、硬直性を呼び戻しました。私たちは何年もの間、名前空間と格闘し、1 文字でも位置がずれていればメッセージを拒否する複雑なエンタープライズサービスバスと戦いました。スキーマのオーバーヘッドが実データより多くの帯域を消費する、「XML の暗黒時代」でした。
そして今、私たちには JSON と REST があります。JSON は本質的に柔軟です。単なるキーと値のマップにすぎません。それでも、エンジニアとしての本能は、その柔軟性を厳格な型(TypeScript のインターフェース、Java の DTO、Avro スキーマ)で包み込もうとします。1970 年代のメインフレームの硬直性を、2020 年代のクラウドイベントに押し付けようとしているのです。なぜでしょうか。未知のものが怖いからです。フィールドが 1 つ欠けただけでサービスがクラッシュするのが怖いのです。しかし、これから見ていくように、その恐れには間違ったツールで対処されています。
財務分析:統合保守の「見えない税」
スキーマの硬直性のコストを定量化してみましょう。中規模から大規模の典型的なエンジニアリング組織では、統合の保守は「静かな危機」です。貸借対照表の項目としては現れませんが、生産性に対する大きな足かせです。
保守コストの計算
探してみれば、パターンは容易に見えてきます。外部 SaaS 統合を数多く抱える組織では、ベンダーが定期的にペイロードを変更し、厳格なパーサーを壊す変更のたびに同じサイクルが始まります。検知、DTO/スキーマの更新、テスト、そして CI/CD によるデプロイです。1 回の破損につき数時間、それが年間で何度も積み重なれば、シニアエンジニアの時間を大きく、継続的に奪うことになります。
しかし本当のコストは機会費用です。シニアエンジニアが Stripe の最新の変更に合わせて DTO を更新している間、ビジネスを前進させる新しい不正検知の自動化機能は作られていません。時間が経つにつれ、この税は累積的なベロシティの喪失へと複利的に膨らみ、より機敏な競合に大きく後れを取る原因になり得ます。
データ流動性の哲学:schema-on-read
SchemaBridge では、根本的な発想の転換を提唱しています。それが schema-on-read です。
データを取り込む瞬間に検証する(schema-on-write)のではなく、まずイベントの生の、階層構造をもつ真実をそのまま取り込みます。JSON ペイロードは、事前に宣言されたスキーマなしで、届いたままの形で実行(run)とともに永続化されます。スキーマ、より正確には変換を適用するのは、特定のビジネスプロセスがそのデータを必要とする瞬間だけです。
データ流動性が勝つ理由
1. ゼロタッチの取り込み:新しいプロバイダーからのイベント受信を数秒で開始できます。プロバイダーの Webhook を SchemaBridge のエントリーポイント(取り込み層が処理)に向ければ、すべての JSON イベントが即座に記録された実行を開始します。データの意味は後から考えればよいのです。
2. 未知への保険:プロバイダーが今日、あなたに不要なフィールドを追加しても、それは生の JSON に記録されています。生のペイロードはご契約プランの保持期間中保管されるため、数週間後にそのフィールドがやはり必要だと気づいた場合でも、生の入力はまだ残っています。プロバイダーに過去のデータを求める代わりに、個々の実行を更新したマッピングでリプレイする(またはそのペイロードを再投入する)ことができます。
3. 疎結合な進化:取り込み層と変換層は、それぞれ異なるスピードで進化できます。エントリーポイントに一切触れることなく、ビジネスロジックを 1 日に何度でも更新できます。
遅延バインディングの数学的根拠
コンピューターサイエンスにおける遅延バインディング(late-binding)とは、識別子や型の解決を実行の瞬間まで遅らせる手法です。Ruby や Python のような動的言語が特定のタスクで強力なのは、このためです。
schema-on-read は、遅延バインディングをデータ基盤に適用したものです。マッピングを遅らせることで、硬直したグラフ(あらゆる変更に完全な再構築が必要)から、柔軟な経路(進みながら地形に適応できる)へと移行できます。
数学的には、$N$ 個のプロデューサーと $M$ 個のコンシューマーの間のマッピングの数は $N \times M$ です。すべてのプロデューサーとコンシューマーが厳格なスキーマに合意しなければならないとすれば、膨大な調整問題を抱えることになります。遅延バインディングを備えたスキーマレスなブリッジを使えば、問題は $N + M$ 個のマッピングに縮小され、それぞれのマッピングはローカルかつ独立しています。これこそが真の水平的なエンジニアリングスケールを実現する方法です。
JSONata:関数型イベント処理のマスタークラス
schema-on-read を実用的にするには、探索のために設計された言語が必要です。私たちは JSONata を選びました。JSONata は単なるクエリ言語ではありません。生の JSON 階層に対して直接動作する、関数型の変換エンジンです。
JSONata 式の構造
注文の一覧を返すレガシー ERP のペイロードを考えてみましょう。各注文は複雑なネスト構造を持っています。金額が 500 ドルを超え、現在 'SHIPPING' 状態にある注文について、すべての部品番号を抽出したいとします。
従来のコード(Javascript):
const parts = payload.orders
.filter(o => o.total > 500 && o.status === 'SHIPPING')
.flatMap(o => o.items)
.map(i => i.partNumber);
このコードは脆弱です。orders が null だったり、ある注文に items がなかったりすると、クラッシュします。
JSONata の真骨頂:
orders[total > 500][status = 'SHIPPING'].items.partNumber
この式はパス耐性があります。items のない注文は単に何も寄与せず、orders がまったくないペイロードは、ヌルポインタによるクラッシュではなく何もない結果になります。データを「前提とする」のではなく、データを「探索する」のです。
正直に、注意点を 2 つ挙げておきます。第一に、JSONata は型について魔法を使うわけではありません。ベンダーが "total": "600" を文字列として送り始めた場合、比較は推測するのではなくエラーを発生させます。第二に、SchemaBridge では、まったく出力を生成しないマッピングは、空の値を下流に渡すのではなくステップを失敗させます。これは意図的な設計です。マッピングが突然何にもマッチしなくなるのは、ほぼ間違いなく上流のデータ形状が変わったサインであり、それは目立つべきだからです。
応用パターン:深い子孫の選択
JSONata の最も強力な機能の 1 つが、子孫演算子 ** です。階層のどこにあっても、任意のキーを見つけられます。
**.tracking_number
下流のプロバイダーごとに追跡番号のネストの仕方が異なっていても、この 1 つの式で、あらゆるバージョンのペイロードからすべてを見つけ出せます。これこそが構造的レジリエンスの定義です。特定のキーを探す脆い作業を、真実を求める柔軟な検索へと変えるのです。
応用パターン:その場でのデータ再構成
JSONata を使えば、JSON オブジェクト全体を 1 回のパスで再構築できます。
orders.{ "order_id": ID, "summary": $join(items.name, ', ') }
従来のコードでは、オブジェクトのマッピング、文字列の連結、配列の反復処理が必要です。JSONata では、望む状態の宣言的な射影になります。複雑なイベントを簡潔にまとめて Slack 通知やモバイルアプリに送る必要がある場合に、特に威力を発揮します。
テーブルレスなデータの運用:セキュリティと検証
批判的な人はよくこう尋ねます。「スキーマを使わないなら、どうやってゴミデータがシステムに入るのを防ぐのか?」
答えは、スキーマレス != 未検証 です。検証をvertex レベルに移すだけです。
- エントリーポイント:事前に宣言されたスキーマなしで任意の JSON ボディを受け付けます。大量のデータを受け入れる入口であり、ビジネスルールを置く場所ではありません。
- チェックステップ:グラフの早い段階にペイロードを検査する変換ステップを置き(例:
$exists(device_id) and $type(reading) = 'number')、その結果に応じて条件付きエッジでルーティングします。不正なデータで停止させたい場合は、チェックによってステップを失敗させます。実行は目に見える形で失敗し、データやマッピングを修正した後、失敗したステップから再試行できます。人の判断が必要なケースでは、Approval ステップが手動レビューのために実行を一時停止します。 - 目立つマッピング失敗:突然何も生成しなくなった JSONata マッピングは、空のデータを下流に渡すのではなくそのステップを失敗させるため、実行はモニター上で赤く表示されます。スキーマの変更を、3 つ先のシステムではなく、壊れたそのステップで捉えられます。
ケーススタディ:50 リージョンにわたるデータ取り込みメッシュ
多数のリージョンからテレメトリを取り込むグローバルな IoT フリートを考えてみましょう。各リージョンでは、センサーファームウェアのバージョンが少しずつ異なります。リージョンごとに独自の JSON の「方言」があるのです。
課題
チームが、厳格なテーブルスキーマを持つ従来型の SQL ベースの取り込みシステムから始めたとします。1 つのリージョンでファームウェア更新が展開されるたびに、そのリージョンの取り込みパイプラインが壊れます。新しいファームウェアが、データベースにカラムが存在しない battery_health_v2 フィールドを追加するからです。データチームは常に「火消し」モードに陥り、多数の本番データベースに ALTER TABLE コマンドを実行し、メンテナンスウィンドウのたびにイベントを取りこぼします。
SchemaBridge によるソリューション
では、同じチームがスキーマレス戦略に移行したと想像してみてください。
1. ユニバーサルな取り込み:すべてのリージョンが、同じ SchemaBridge のエントリーポイントにデータを送ります。取り込み層はスキーマを気にせず、生のイベントをそれぞれの実行とともに永続化します。
2. 読み取り時のマッピング:チームはファームウェアバージョンごとに正規化ステップを作成します。条件付きエッジが、各生イベントをファームウェアバージョンのフィールドに基づいて適切なステップへルーティングします。
3. ノーコードでのアップグレード:新しいファームウェアバージョンがリリースされても、更新すべきデータベースはありません。チームは既存のステップを複製し、新しいフィールドを含むよう JSONata マッピングを更新して、デプロイするだけです。
想定される結果
- ダウンタイム:ファームウェア更新によって、データチームが取り込みのダウンタイムを強いられることはなくなるでしょう。
- データの完全性:まだ処理する準備ができていないフィールドが含まれていても、チームは生のセンサーデータを取り込めるでしょう。
- エンジニアリングの集中:DB マイグレーションから解放されたデータチームは、取り込んだ生データに基づく予知保全に集中できるでしょう。さらに、「余分な」フィールドは捨てられずに記録されているため、硬直した schema-on-write のシステムなら捨ててしまっていたであろう、バッテリー不良の兆候のような問題も発見できる可能性があります。
結論:ベロシティは設計上の選択である
厳格なスキーマは選択です。動的なベロシティよりも静的な安全性を優先するという選択です。閉じたシステムでは、それは妥当な選択です。しかし、つながり合った分散エコシステムでは、それは失敗へとつながる選択です。
予測不可能で、進化し続け、階層的な、ありのままの世界に合わせてシステムを設計しましょう。ベロシティのために設計しましょう。SchemaBridge で設計しましょう。データの流動性を受け入れることで、硬直した DTO とデータベースマイグレーションに縛られた競合他社が夢見ることしかできないスピードで、構築し、スケールし、イノベーションを起こす力が手に入ります。
関連記事:ファンアウトを極める — Spawner vertex によって取り込みから実行へ。大量の処理を多数のアイテムに分散し、バックプレッシャーを一定の範囲に抑えます。