FLARES LLC

Technical document

CUEを正本とした型生成パイプラインとBazelによる堅牢・高速なビルド基盤

API、データベース、フロントエンド、バックエンド、MCP Tool Schemaがそれぞれの言語で管理されると、仕様変更のたびに同期確認が必要になります。CUEを業務上の契約の正本にし、生成・検証・ビルドをBazelの依存グラフへ載せることで、変更の影響範囲を小さく保ちながら、複数サービスを安全に更新できます。
開発基盤 / 型生成・ビルド約22分公開日 2026年8月21日更新日 2026年8月21日
一つの契約定義から複数のスキーマとコードを生成し、依存グラフで検証する開発基盤のイラスト

Summary

この文書の要点

  • CUEを型・制約・デフォルト値・業務契約の正本とし、各言語の型や外部スキーマは派生成果物として扱います。
  • Bazelは型を定義するものではなく、CUEからの生成、テスト、コンパイル、パッケージングを依存グラフで実行する基盤です。
  • 生成物を直接編集せず、再生成後の差分ゼロをCIで確認することで、実装と生成物のドリフトを検出します。
  • 変更された契約と、その契約に依存するターゲットだけを検証・ビルドし、キャッシュと並列実行でフィードバック時間を短くします。

1. 定義が増えるほど、同期作業が開発の中心になる

API定義、DB定義、フロントエンドのTypeScript型、バックエンドのGo型、MCP Tool Schemaが、それぞれ別のファイルや言語で管理される構成は珍しくありません。形式が違うこと自体は問題ではありませんが、同じ業務上の概念を複数箇所へ重複して記述する状態が続くと、変更時の確認対象が増えます。

たとえば、注文の状態に「保留」を追加した時、APIの列挙値だけでなく、DBの制約、Goのswitch、TypeScriptのunion、画面の表示、MCPツールの入力説明まで追随させる必要があります。一部を変更しても他の定義が古いまま残れば、コンパイルが通っても実行時に別の意味で解釈されることがあります。

手作業による同期と確認が増えると、仕様変更の価値よりも整合性確認の負担が目立ちます。さらに、複数のAIエージェントがコードを生成する環境では、どの定義を基準に判断したのかを後から説明しにくくなります。そこで、業務上の契約とデータ構造を正本として一つに集約し、そこから各形式を機械的に生成する考え方を採用します。

  • 正本にするのは、特定の言語のソースコードではなく、複数の実装が共有する契約です。
  • API、DB、UI、エージェントツールの定義は、同じ契約から導出できる派生成果物として扱います。
  • 生成物の修正ではなく、正本または明示的な変換定義を変更して再生成します。

2. CUEとは何か:型、値、制約を同じ世界で扱う

CUEは、構造化データの型や制約を記述し、その制約にデータを適合させたり、具体的な値を出力したりするための言語とツールです。CUEでは型も値もCUEの値として扱われます。そのため、単なる型定義ファイルと実データを別々の仕組みで扱うのではなく、両者を統合して評価できます。

制約、列挙、範囲、正規表現、必須・任意フィールド、デフォルト値、フィールド間の関係を表現できます。たとえば、状態を列挙し、金額を0以上に制約し、終了日時が開始日時より後であることを定義する、といった契約を一つのモデルにまとめます。複数の定義をunification(統合)すると、矛盾する値はエラーとして表面化し、矛盾しない制約は一つの定義へ集約されます。

`cue vet`はCUEやJSON、YAMLなどのデータを制約に照らして検証します。`cue export`は評価結果をJSONやYAMLなどの具体的なデータとして出力します。OpenAPI、JSON Schema、Protocol Buffers、SQL、MCP Tool Schemaへ出力する場合は、対象形式に合わせた変換定義や生成器を用意します。CUEがすべての形式を同じ品質で自動変換するという意味ではありません。

一般的な設定テンプレートは、変数を置き換えて最終ファイルを作ることに重点を置きます。一方、CUEは検証を中心に置き、型と値、制約とデフォルトを統合して、構成が成立するかを評価します。ただし、CUEを万能なアプリケーション言語として扱うべきではありません。複雑な業務処理、状態遷移の実装、外部サービスとの通信、画面の振る舞いは、GoやTypeScriptなどの実装言語に残します。

CUEで契約と制約を定義する例
#Order: {
  id:        string
  status:    "draft" | "confirmed" | "cancelled"
  total:     >=0 & number
  currency:  *"JPY" | "JPY" | "USD"
}

order: #Order & {
  id:     "order-001"
  status: "confirmed"
  total:  1200
}
検証と具体値の出力
cue vet ./cue ./data/order.yaml
cue export ./cue -e order --out json > generated/order.json

3. CUEを正本にする理由:ソースコードではなく契約を正とする

Single Source of Truth(正本を一つにする考え方)の目的は、ファイル数を減らすことではありません。業務上の契約がどこに定義され、どの成果物がそこから派生したのかを明確にすることです。Goの型を正本にしてTypeScript型を生成する方法もありますが、その場合はGoに表現しにくいUI向けの制約や、APIとDBの差分が型定義へ入り込みます。

CUEを上位正本にすると、仕様の重複を減らし、変更箇所を一元化できます。正本から同じ入力を与えて生成すれば、生成物の再現性も確認しやすくなります。さらに、制約違反をコード生成前に検出できるため、生成された大量のコードを調べてから問題に気づく状態を避けられます。

AIによるコード生成とも相性があります。AIに自由にデータモデルやAPIの意味を設計させるのではなく、CUEで定めた契約、命名、許可された依存方向に従って実装を生成させます。複数顧客、複数アプリ、複数ドメインへ展開する場合も、共通契約と拡張契約を分け、個別差分を上位モデルへ明示できます。

このとき、CUEにすべてを押し込む必要はありません。正本は業務上共有される構造と制約に限定し、認証方式の細部、DBのインデックス、UIの状態管理、外部APIのリトライなど、実装固有の事項は各実装側へ残します。正本の境界を狭く保つことが、過剰な共通化を避ける条件です。

  • 正本: 業務上の概念、フィールド、制約、デフォルト、契約バージョン、拡張点。
  • 派生成果物: OpenAPI、JSON Schema、Protocol Buffers、SQL、MCP Tool Schema、各言語の型とクライアント。
  • 手書き実装: 画面の振る舞い、ユースケース、永続化の最適化、外部システム固有の処理。

4. 型生成パイプラインの全体像

パイプラインの入口は、要件とドメイン定義です。そこからCUEモデルを作り、`cue vet`で構文・型・制約を確認します。検証を通ったモデルを`cue export`または専用の生成器へ渡し、OpenAPI、JSON Schema、Protocol Buffers、SQL、MCP Tool Schemaを作ります。各形式は用途が異なるため、変換定義には型の対応、必須性、列挙、説明、互換性ルールを明示します。

次に、Go・TypeScript・gRPCクライアント・sqlc向けの入力・MCPサーバーの型やハンドラーの雛形を生成します。生成後のコードを手書きの実装と組み合わせ、Bazelのターゲットとして登録します。生成、静的解析、テスト、コンパイル、パッケージングの順番は、スクリプトの呼び出し順ではなく、Bazelの依存関係として表現します。

本番で実装差異が見つかった場合や、新しい変更要求が出た場合は、生成物や個別実装をその場で修正して終わりにしません。要件またはCUEモデルへ戻し、契約として採用するのか、特定サービスの実装事情として閉じるのかを判断します。この戻り道を設計しておくと、運用で見つかった知識が正本へ還流します。

  • CUE: 何を作るべきか、どの値が許されるかを定義する。
  • 生成器: 正本を対象形式へ写像する。形式ごとの変換ルールは明示的に管理する。
  • Bazel: どの入力からどのターゲットを実行し、どの成果物へ到達するかを管理する。
  • 検証・デプロイ: 生成差分、契約適合、実行可能な成果物を順に確認する。
CUEを正本にした生成・検証・デプロイの流れ
flowchart LR
  subgraph CUE["CUE:契約・制約の正本"]
    R["要件・ドメイン定義"] --> M["CUEモデル"]
  end
  subgraph GEN["生成:派生成果物"]
    M --> V["cue vet"] --> E["cue export / 変換定義"]
    E --> S["OpenAPI / JSON Schema / Protobuf / SQL / MCP Schema"]
    S --> C["Go・TypeScript・gRPC・sqlc・MCPコード"]
  end
  subgraph BAZEL["Bazel:依存グラフ・実行基盤"]
    C --> T["生成・テスト・ビルド target"]
    T --> D["差分実行・キャッシュ・並列化"]
  end
  subgraph CHECK["検証:品質ゲート"]
    D --> K["契約・適合テスト"]
    K --> Z["生成差分ゼロ"]
  end
  subgraph DEPLOY["デプロイ:成果物"]
    Z --> P["バイナリ・コンテナ・静的ファイル"] --> Q["デプロイ"]
  end
  N["生成物は直接編集しない"] -.-> S
  Q -. "実装差異・変更要求" .-> R

5. CUEとBazelを組み合わせる理由

CUEは型、値、制約を評価し、定義を別形式へ変換する役割に向いています。しかし、複数言語のコンパイル、コード生成、テスト、コンテナ化、静的ファイルのパッケージングを、リポジトリ全体の依存関係として管理する役割までCUEだけに持たせると、実行基盤の責務が大きくなります。

Bazelは、入力、ルール、出力、依存ターゲットからなるビルドグラフを管理します。CUEの生成ターゲットを起点に、OpenAPI生成、GoとTypeScriptのコンパイル、契約テスト、コンテナイメージ生成までを一つのグラフへ載せられます。CUEが型を定義し、Bazelがその定義を使う処理の依存と実行を管理する、という分離です。

グラフが正確なら、変更された契約とそのreverse dependency(逆向きの依存)だけを実行できます。同じ入力とルールから得られる結果はキャッシュでき、独立した生成・テストは並列に進められます。ローカルとCIで同じBUILD定義を共有できるため、環境ごとに手順が変わる問題も抑えられます。

再現可能なビルドを実現するには、Bazelだけでなく入力の固定、ツールチェーンの固定、ネットワークや時刻への暗黙依存の排除も必要です。Bazelはその前提を実行規則に落とし込みやすくする基盤であり、正本や生成器が誤っていても自動的に正しいものへ直す魔法ではありません。

  • CUE: 型定義、制約、データ構造、契約の統合と検証。
  • Bazel: 依存関係、生成タスク、差分ビルド、差分テスト、キャッシュ、並列実行、成果物生成。
  • 境界: CUEはビルド全体を担当せず、Bazelは業務上の型や制約を正本として定義しない。
Bazelターゲットの概念例
cue_export(
    name = "order_schema",
    srcs = ["//contracts:order.cue"],
    outs = ["order.openapi.json"],
)

go_library(
    name = "order_contract_go",
    srcs = [":generated_order_go"],
    deps = [":order_schema"],
)

go_test(
    name = "order_contract_test",
    deps = [":order_contract_go", "//test:contract_harness"],
)

6. CI/CDで検証を段階化する

CIでは、いきなり全サービスをビルドするのではなく、契約の妥当性、生成の再現性、実装の適合性、配布物の成立性を段階に分けます。早い段階で失敗させることで、コード生成やコンテナビルドのログを大量に調べる前に、原因を正本側へ戻せます。

第一段階ではCUEの構文・型・制約を検証します。第二段階でCUEからスキーマとコードを生成し、第三段階でワークツリーを汚さない生成を行ったうえで、Git管理対象の生成物に差分がないことを確認します。生成物をGitに含める場合はレビューで差分を読める利点がありますが、生成元との一致を必ずCIで検査します。

第四段階以降はBazelで静的解析、変更範囲に応じたテスト、契約テストを実行します。契約テストでは、APIの入力・出力、エラー形式、互換性、DB境界、MCPツールの引数と結果を確認します。テナント分離、認可、冪等性、再実行時の重複登録など、型だけでは保証できない適合条件も、Bazelのテストターゲットとして契約に近い場所へ置きます。

最後にバイナリ、コンテナ、静的ファイルを生成し、デプロイ前に対象環境の設定、マイグレーション、イメージの起動、ヘルスチェック、ロールバック可能性を確認します。生成物をGitへ含めない運用では、CIが毎回生成して成果物として保存しますが、どちらの運用でも生成元と生成器のバージョンを固定し、同じ入力から同じ出力になることを検証します。

  • 1. CUE構文・型・制約の検証
  • 2. CUEからのスキーマ・コード生成
  • 3. 再生成後の差分ゼロ確認
  • 4. Bazelによる静的解析
  • 5. 変更範囲に応じた単体・統合テスト
  • 6. API・DB・MCPの契約テスト
  • 7. テナント分離・認可・冪等性などの適合テスト
  • 8. バイナリ・コンテナ・静的ファイルの生成
  • 9. デプロイ前の設定・起動・ヘルスチェック・ロールバック確認
生成物を直接編集していないことを確認する例
bazel run //contracts:generate_all
git diff --exit-code -- generated/   || { echo "生成物はCUEから再生成してください"; exit 1; }

bazel test //contracts/changed:all   --test_output=errors

7. AIエージェント開発との相性:自由度を制約して速度を安全に高める

AIエージェントがコードを大量に生成する環境では、実装量を増やすことより、生成された変更を同じ契約と検証規則へ収束させることが重要です。AIに自由にデータモデルや依存関係を設計させるのではなく、CUEの契約、生成器の出力、Bazelの許可された依存方向に従わせます。

AIが生成したコードは、Bazelの静的解析、型チェック、単体テスト、契約テスト、適合テストというゲートを通します。生成物を直接書き換えた場合は、再生成差分ゼロのチェックで検出します。依存方向をBazelのvisibilityやターゲット構造で制御しておけば、画面からDB内部実装へ直接依存するような変更も、レビュー前の段階で止められます。

変更は小さな単位へ分け、変更された契約とその影響範囲だけを差分テストします。失敗時には、どのCUE契約、どの変換定義、どのBazelターゲットが壊れたのかをログとターゲットラベルで特定できるようにします。これにより、AIに再試行させる場合も「何となく直す」のではなく、失敗した境界を指定できます。

並列エージェントが別々のサービスを変更する場合も、共通の正本とビルド規則へ収束させます。各エージェントが個別の型や例外を増やすのではなく、契約変更が必要ならCUEへ、実装固有ならサービス境界へ戻します。AIの自由度を制約することは速度を落とすためではなく、後から人が整合性を確認する範囲を小さくし、安全に並列化するための設計です。

  • AIに任せる範囲: 契約に従った実装、テスト、変換定義の候補作成、局所的な修正。
  • 人が判断する範囲: 契約の意味、互換性、認可、テナント境界、例外を正本へ昇格させるかどうか。
  • 機械的なゲート: 再生成差分、Bazel依存方向、静的解析、差分テスト、契約・適合テスト。

8. メリットと限界

この構成のメリットは、仕様と実装の乖離を早期に見つけ、型定義の重複を減らせることです。契約からの依存グラフを持てば、変更影響範囲を把握しやすくなり、差分ビルドと差分テストで日常のフィードバックを短くできます。生成の手順と入力が固定されるため、担当者が変わっても保守と引き継ぎの前提を説明しやすくなります。

一方、CUEの学習コストは無視できません。unification、制約、デフォルト、具体値の違いを理解しないまま、設定テンプレートの置き換えとして使うと、意図しない統合や未確定値を見落とします。すべてのドメインを最初から完全にモデル化しようとすると過剰設計になるため、変更頻度が高く、複数サービスが共有する契約から始めます。

生成対象ごとに変換ルールの設計も必要です。Protocol Buffersのフィールド番号、OpenAPIのnullable、JSON Schemaの追加プロパティ、SQLのNULLやインデックス、MCP Tool Schemaの入力説明は、単純な型対応だけでは決まりません。どこまで共通化し、どの情報を形式固有の拡張として持つかを定義します。

Bazelにも導入・運用コストがあります。BUILD定義、ルール、ツールチェーン、リモートキャッシュ、CI権限を整備する必要があり、生成物と手書きコードの境界も設計しなければなりません。CUEに表現しにくい実装固有の事情も残ります。正本に誤った仕様を定義すれば、誤ったコードを大量生成します。自動生成はレビューを不要にするものではなく、レビュー対象を正本、変換ルール、生成差分、重要な手書き実装へ整理するものです。

  • メリット: 仕様と実装の乖離を抑え、重複を減らし、変更影響と差分ビルドを扱いやすくする。
  • メリット: 並列開発、再現可能な生成、保守、引き継ぎに向く。
  • 限界: CUEとBazelの学習・運用コスト、変換ルール、境界設計が必要。
  • 限界: 誤った正本は誤った生成物を増やし、自動生成後もレビューと適合確認は必要。

9. Googleとの関係をどう理解するか

CUEはGoogleが全社的に採用している言語だ、と断定できるものではありません。CUEの言語仕様と紹介資料では、CUEがGoogle社内のGCLやBCLなどの構成管理言語から影響を受けたことが説明されています。これは設計思想の系譜を示すものであり、Googleの社内システムをそのまま利用できることや、同じ運用が公開されていることを意味しません。

Googleの公開技術として代表的なのは、Protocol Buffers、Bazel、Kubernetes、CELなどです。Protocol Buffersは構造化データのシリアライズとサービス間契約に、Bazelはビルドとテストに、Kubernetesはコンテナ化されたワークロードの管理に、CELは組み込み可能な式評価に使われます。それぞれは役割の異なる技術であり、CUEと一体の製品として扱うものではありません。

今回の構成は、Googleの社内構成を再現するものではありません。宣言的な契約、コード生成、依存グラフ、再現可能ビルドという思想を、CUEとBazelを中心に自社向けへ再構成する設計です。Protocol BuffersやKubernetesを採用する場合も、組織の規模、既存API、運用体制、チームの学習コストを踏まえて個別に判断します。

10. 自社での採用方針

自社では、CUEをドメイン契約と制約の上位正本とします。Protocol Buffers、OpenAPI、JSON Schema、SQL、MCP Tool Schemaは、CUEから導出される派生成果物です。出力形式の都合で追加情報が必要な場合は、変換定義として明示し、生成物へ手作業で追記する運用は原則として採用しません。

Bazelは、生成・テスト・ビルド・パッケージングの実行基盤とします。CUEの検証、各種スキーマ生成、Go・TypeScriptコード生成、gRPCやsqlcの処理、契約・適合テスト、バイナリ・コンテナ・静的ファイル生成をターゲット化し、変更された範囲だけを実行できる依存グラフへまとめます。

生成物は原則として手動編集しません。変更はCUEまたは明示的な変換定義から行い、生成差分、依存関係、テスト結果をCIで検証します。顧客別・アプリ別の差分は、共通契約と拡張契約を分離して管理し、個別要件が共通契約を不必要に汚染しない境界を保ちます。

この採用方針は、将来的なAIエージェントによる並列開発にも対応します。複数のエージェントが同時に実装しても、共通のCUE正本とBazelのビルド規則へ変更を収束させます。自社の技術思想は、「宣言を正本にし、生成と検証を自動化し、Bazelで安全に収束させる」です。

  • CUE: ドメイン契約、型、制約、デフォルト、共通契約と拡張契約。
  • 派生成果物: Protocol Buffers、OpenAPI、JSON Schema、SQL、MCP Tool Schema、言語別コード。
  • Bazel: 生成、テスト、コンパイル、パッケージング、差分実行、キャッシュ、並列化。
  • CI: 正本からの再生成、差分ゼロ、依存方向、契約適合、成果物の成立性。

11. まとめ:仕様とビルドを一つの依存グラフへ置く

CUEを正本にする設計は、ファイルの数を減らすためだけの仕組みではありません。業務上の契約を一つの場所で定義し、そこから複数のスキーマとコードを再現可能に導出することで、仕様変更の手戻りと生成物の不整合を早い段階で検出するための設計です。

Bazelを組み合わせると、CUEからの生成、静的解析、差分テスト、契約・適合テスト、バイナリやコンテナの生成までを、依存関係に従う処理として扱えます。必要な範囲だけをキャッシュと並列実行の対象にできるため、開発速度と堅牢性を同じ基盤で高めやすくなります。

CUEは「何を作るべきか」を定義し、Bazelは「どの依存関係に従って、どこまでを、どのように検証・生成するか」を管理する。この区別を保てるなら、CUEとBazelの組み合わせは、単なるツール選定ではなく、仕様・生成・検証・ビルドを一つの依存グラフに統合するための開発基盤設計になります。

Technical documents

技術文書を増やしていきます。

AI、クラウド、業務アプリ開発、要件定義、運用設計に関する考え方を、今後も文書として整理します。

技術文書一覧へ