複数の独立したプロダクトやサービスを組み合わせるとき、避けられない判断がある。データを集めるシステムと、それを消費するシステムをどこで分けるかだ。
最初は「同じモジュールに書いたほうが効率的では」という誘惑に駆られる。しかし、その判断がもたらす後悔は大きい。収集ロジックが変わるたびに利用側が壊れ、利用側の要件が変わるたびに収集側に手を入れる。変更コストが落ちない設計は、組織の負債となる。
この記事では、収集と利用を徹底的に分離する設計パターンを、その実装アプローチと運用を通じて整理する。
1. 課題:責務混在による変更の破壊性
スクレイピングやデータベース連携を扱うシステムでよく起きるのは、次のような光景だ。
「データ収集パイプラインが、集めたデータをそのまま利用層向けに整形する」
短期的には効率的に見える。ファイルを跨がず、一連のプロセスで完結する。しかし、数ヶ月後に収集元の仕様が変わったとき、ツケが来る。
具体例:金融データを複数のソース(証券取引所、企業の決算説明資料、株価データベース)から集めるシステムがあるとしよう。最初は、収集スクリプトが取得したデータをそのまま整形し、マスターテーブルに格納していた。ところがある日、取得元の URL 構造が変わり、パーサーを全面改修する必要が生じた。
その過程で「ちょっと待てよ、このカラムの順番、利用側が期待している順序と違わないか」という問題が浮上する。収集側を修正しながら、同時に利用側の参照部を追い直さなければならない。
逆方向でも同じ。利用側(例えば、可視化やレポート生成のシステム)が「このデータもあったらいいな」と新しいカラムを要求すると、収集側のスクリプト全体に影響が波及する。
これは設計の根本的な問題だ。責務の境界が曖昧だと、どちらか一方の変更がもう一方に自動的に破壊をもたらす。
2. パターン定義:責務の完全分離
解決策はシンプルにして明確だ。
収集層の責務:データソースから情報を取得し、一貫した構造で蓄積する。品質と完全性にのみ責任を持つ。
利用層の責務:蓄積されたデータを読み取り専用で参照し、自層の目的に応じて消費する。データ構造の上流変更に依存しない。
この分離の鍵は、層間のインターフェースを明確に定義することにある。
データベーステーブル、API スキーマ、スプレッドシートのカラム構造——形は何でもいい。重要なのは、インターフェースが明示的で、変更を追跡できることだ。
3. 設計の核:スプレッドシートを選んだ理由とトレードオフ
八雲では、データ収集基盤(medallion と呼ぶ)と動画生成エンジン(montage)を独立したプロジェクトとして運用している。両者の間を繋ぐインターフェースは、共有 Google スプレッドシートだ。
なぜスプレッドシートか。一見、原始的に思えるこの選択には、実装上の強い利点がある。
メリット:
-
スキーマを明文化しやすい:カラム名、型、必須フィールドをスプレッドシート上で一目で確認できる。「新しいカラムを追加するときは必ず仕様書を更新する」というルールを自然と適用できる。
-
インターフェース変更が検視可能:REST API のエンドポイント設計や DB スキーマの ALTER TABLE と異なり、スプレッドシートの変更は人が読めて、差分が明確だ。「このバージョンでは何が変わったか」を履歴で追える。
-
将来の API 移行パスが開かれている:後述するが、スプレッドシートを介した責務分離を確立すれば、REST API や MCP サーバへの拡張は「インターフェース層を追加するだけ」で済む。コアロジックを書き直す必要がない。
デメリット・トレードオフ:
-
スケールの限界:HUMAN_INPUT — スプレッドシートの行数制限やセル数上限は、大規模運用では制約になる可能性がある(具体的な限界値や現在の運用データ規模の詳細が必要)。
-
同時実行性の欠如:複数のプロセスが同時にスプレッドシートに書き込むとき、排他制御がない。HUMAN_INPUT — 実際の同時実行数や応答時間の影響測定があれば、トレードオフを定量化できる。
-
リアルタイム性:API なら HTTP で即座にアクセスできるが、スプレッドシートは Google Workspace の同期遅延を被る。HUMAN_INPUT — 実運用での遅延時間データがあれば根拠が強まる。
それでも八雲がこれを選んだのは、「シンプルさ」がもたらす設計の堅牢性が、スケールのハンディキャップを上回ると判断したからだ。スキーマを極限まで簡潔に保つことで、責務分離の原則が破られにくくなる。
4. Yakumo での適用:medallion と montage の実装パターン
収集層(medallion):証券取引所の決算短信、企業の決算説明資料、各種財務データベースから構造化情報を抽出。銘柄属性マスターと、年度別タブ(決算期ごとに損益計算書、貸借対照表、キャッシュフロー計算書を蓄積)で構成されるスプレッドシートに書き込む。約 3,800 社、過去 5 年分の対応を見込む。
利用層(montage):スプレッドシートを読み取り専用で参照し、動画コンテンツを生成。特定銘柄の特定年度のデータを取得する薄いクライアント(sheets_io.py)を経由して、型付きの構造体として利用層に返す。
このクライアントが「薄い」ことが決定的に重要だ。データ取得と返却だけを担い、変換・整形ロジックを一切含まない。ロジックをクライアントに書き始めた瞬間、責務の境界が崩れる。
他のツール(GitHub 上の hub リポジトリ、xbrl-parser)の成果も、このクライアント経由で統一的にアクセスできる。タブ構造が増えても(管理ツール sheets-yearly-tabs で自動化)、利用側のコードは変わらない。
5. 運用が明かした落とし穴と解決策
落とし穴 1:クライアント層への責務汚染
「ちょっと整形するだけ」という小さな判断から始まる汚染。クライアント側に「スプレッドシートのカラム A と B から計算値 C を導出する」というロジックが混入すると、その瞬間から収集側の変更が利用側に波及し始める。
解決策:「取得と返却のみ」というルールを明文化し、コードレビューでチェックリストに加えた。
落とし穴 2:スプレッドシートが暗黙のスキーマ化
カラム名、並び順、タブ名が「事実上のインターフェース契約」になるのは避けられない。しかし、これを明示的に文書化しないと、「なぜこの順序なのか」「このカラムは何を表すのか」という疑問が後発のメンバーに生じ、スキーマ変更が怖くなる。
解決策:specs/system/earnings-schema.md にスキーマを明文化。新しいカラムを追加するときは、必ず先に仕様書を更新してから、スプレッドシートに反映するというプロセスに統一した。
落とし穴 3:冪等性の欠如による重複書き込み
launchd による自動実行と手動実行が並走するとき、同じデータが二重に格納される問題が起きた。銘柄×年度×四半期をキーにした冪等写入に統一することで解決した。
HUMAN_INPUT — 実際の重複発生頻度や、冪等性の実装にかかったコスト(工数・複雑性の増加)があれば、より説得力がある。
6. 将来への道:REST API と MCP サーバへの拡張余地
収集と利用を分離した設計は、将来の拡張に強い。
現在は gws CLI 経由のシェルベースだが、medallion に REST API 層を被せることを検討している。GET /earnings/{ticker}/{year}/ のようなエンドポイントを公開すれば、ブラウザや外部ツールからも直接アクセスできる。
さらに先の未来では、MCP(Model Context Protocol)サーバとして公開することも視野に入れている。Claude Code から直接金融データを参照しながらコンテンツを生成する——そうした流れがスムーズに繋がる。
重要なのは、どの拡張を選んでも、medallion のコアロジック(収集・パース・蓄積)を書き直す必要がないということだ。インターフェース層の追加だけで済む。
責務分離が成功している設計とは、「変更に強い」のではなく「層を追加しやすい」状態だ。
まとめ
収集と利用の分離は「いずれやるべきリファクタリング」ではなく、最初から設計に組み込むべき原則だ。
独立したプロジェクトとして物理的な境界を引き、その間を明確で変更追跡可能なインターフェースで繋ぐ。スプレッドシートのようにシンプルなメディアを選ぶことで、スキーマの明文化が強制される。複雑なインターフェース設計は不要だ。
八雲の事例では、このアプローチによって収集パイプラインのリファクタリングが怖くなくなり、利用側は「データ構造が安定している」という前提のもとで機能追加に集中できるようになった。
何より、責務を分離しておいたからこそ、将来の拡張パス(REST API、MCP サーバ、さらには外部への提供)が塞がれていない。シンプルな設計は、スケールの余地も残す。