


1つのAPIを複数の言語で使えるフレームワークを開発する
1つのコードから複数の言語向けのパッケージを作るフレームワークを開発する時に使う。プロセスの間のJSONのやり取り、公開してよい型、コードの生成、全部の言語で同じ動きになるかを確かめるテストの決まりを示す。
このAIルールを使うには
## どの言語でも同じ動きと後方互換性を保つ
言語横断型フレームワークでは、実装言語で利用できるあらゆる慣用表現ではなく、対応するすべての言語で理解可能かつ同等に保たれる振る舞いを重視する必要がある。
### 中核となる不変条件
- アセンブリ形式、ランタイムプロトコル、公開APIの後方互換性を維持する。
- 対応するすべての言語ターゲットで、観測可能な振る舞いが同等になることを必須とする。
- 各ターゲット言語の通常の命名規則、パッケージ規則、型規則に従うコードを生成する。
- すべてのターゲットで忠実に表現できるソース言語の機能だけを受け入れる。
- ターゲット固有の性能上の近道よりも、正しさと相互運用性を優先する。
### パッケージ境界
- プロトコルカーネル、ホストランタイム、スキーマ定義、コードジェネレーター、リフレクションツール、互換性チェック、テストフィクスチャを分離する。
- ビルドユーティリティと適合性レポートの生成機能は、ランタイムパッケージの外に置く。
- 各言語ランタイムと任意の統合機能を、それぞれ独立して管理するパッケージとして扱う。
- すべてのコンパイラー、ジェネレーター、ランタイムテストが同じ振る舞いのケースを利用できる場所に、共有フィクスチャを配置する。
- 別々のリポジトリで保守されるコンポーネント同士は、明示的にバージョン管理された依存関係を介して連携させる。
## 処理はカーネルのプロセスで動かし、各言語とJSONでやり取りする
ホスト言語が実装言語を直接実行できない場合は、二面構成のランタイムモデルを使用する。ホストプロセスは標準入力と標準出力を介してJSONメッセージでカーネルプロセスと通信し、実装言語の実行はすべてカーネル内に留める。
### プロセス境界
- すべてのリクエストに、操作を識別する明示的な識別子を持たせる。
- アセンブリの読み込み、オブジェクトのライフサイクル、メソッド呼び出し、プロパティへのアクセス、非同期処理、統計情報、コールバックを、それぞれ別のプロトコル操作で扱う。
- ホストランタイムがコアプロセスに必要なファイル記述子を維持できない場合に限り、追加のラッパープロセスを使用する。
- 診断情報はエラーストリームへ送り、プロトコルストリームを壊さないようにする。
### コールバックの手順
実装言語のコードが、ホスト言語でオーバーライドされたメソッドまたはプロパティに到達した場合は、次のように処理する。
1. カーネルは元のレスポンスの代わりに、コールバックリクエストを送信する。
2. ホストはオーバーライドを実行し、結果または失敗のいずれかを含む完了メッセージを返す。
3. カーネルは元の操作を再開し、その操作の実際のレスポンスを返す。
### プロトコルの進化
- リクエスト名、フィールドの意味、レスポンスの順序、コールバックの順序を互換性契約として扱う。
- プロトコルを変更する場合は、すべてのホストランタイムを同時に更新する。
- メッセージ形式やライフサイクルの規則を変更する前に、互換性テストを追加する。
## 言語の間で、値をコピーで渡すか参照で渡すかを決める
移植可能な値の各カテゴリにワイヤ表現を定義し、所有権の意味を明示する。値渡しで境界を越える値と、カーネルが所有し続けるオブジェクトを混同してはならない。
### ワイヤ表現
- 真偽値、数値、文字列は、それぞれのJSON表現を使って値渡しする。
- リストとマップは値渡しし、他のオブジェクトと区別する必要がある場合は明示的なタグ付きエンベロープを使用する。
- 列挙型は、完全修飾されたメンバー識別子でエンコードする。
- 日付は、タグ付きで曖昧さのないタイムスタンプ表現にエンコードする。
- データ専用構造体は、完全修飾された型とフィールドデータを使ってエンコードする。
- クラスインスタンスは、安定したオブジェクト識別子を使って参照渡しする。
### 境界の規則
- 言語間で、UTF-8文字列、浮動小数点の振る舞い、null許容性、コレクションのコピーに関する意味論を維持する。
- 利用側が依存するようになったタグ付きワイヤキーは、その後も安定させる。
- プロトコルがリモートのライフサイクル管理を担う場合は、オブジェクト参照を明示的に解放する。
- 実装言語側だけでなく、すべてのホストランタイムを通した往復処理をテストする。
## 公開する型を、どの言語でも表せる物に限る
モジュールのメインエントリーポイントから公開される宣言には移植性の制約を適用し、内部実装コードでは、より広範な言語機能の使用を認める。
### 移植可能なAPI公開面
- プリミティブ、リスト、マップ、列挙型、日付、クラス、インターフェース、データ構造、Promise、不透明な値は、すべてのターゲットに妥当な表現がある場合に限って対応する。
- 明示的な命名規則またはスキーマ規則によって、振る舞いを持つインターフェースとデータ専用構造体を区別する。
- データ専用構造体には、読み取り専用プロパティのみを持たせ、メソッドは持たせない。
- Promiseは、メソッドの戻り値型など、すべてのターゲットが一貫して公開できる位置でのみ許可する。
- 静的型付けのターゲットがより精密な型を復元できない場合は、不透明な値を暗黙的にnull許容として扱う。
- クラスは参照渡しし、値のように扱う構造体は値渡しする。
### 制約
- タプル、値を持たない型、任意精度整数、シンボル、メソッドのオーバーロードなど、対応していない公開構造を拒否する。
- オーバーライドするメソッドには、厳密に互換性のあるシグネチャを要求する。共変戻り値や反変引数には依存しない。
- 静的型付けのターゲットで型なしオブジェクトへ縮退する場合は、ユニオン型を避ける。
- 振る舞いを持つインターフェースとデータ専用構造体の相互継承を許可しない。
## 同じコンパイル済みアセンブリから、言語ごとのパッケージを作る
各ターゲット言語のパッケージが単一のAPI正本を共有するように、同じコンパイル済みアセンブリから各ターゲットを生成する。
### 生成パイプライン
1. コンパイル済みの実装言語パッケージからアセンブリを読み込む。
2. 選択したターゲット言語向けに、その言語らしいソースを生成する。
3. その言語のツールチェーンで生成したソースをコンパイルする。
4. ターゲットのエコシステムで標準となるパッケージマネージャーを使って成果物をパッケージ化する。
### ターゲット設計
- 各言語ターゲットに、共有の走査抽象化を基盤とする専用アダプターを用意する。
- 新しいターゲットを追加する際は、成熟したターゲットを参考にしつつ、新しい言語の慣用表現を維持する。
- ランタイムアーキテクチャで必要な場合に限り、元の実装成果物を同梱する。
- 下流のリフレクションツールや生成ツールに必要なアセンブリメタデータを含める。
- 書式設定、インデント、ファイル作成、コードブロックのユーティリティは、ターゲット間で共有する。
- ターゲットを変更した場合は、生成コードのスナップショットを更新する。
## モノレポの中で、バージョンを固定したツールでビルドとテストをする
インストール、ビルド、テスト、lint、スナップショット更新、適合性レポート生成は、リポジトリに固定されたワークスペースツールチェーンを介して実行する。
### 環境と範囲
- 完全な初期セットアップとリポジトリ全体の検証は、リポジトリのルートから開始する。
- 対象を絞って反復作業を行う場合は、パッケージディレクトリからパッケージ単位のスクリプトを実行する。
- 宣言されているパッケージマネージャーとオーケストレーションツールのバージョンを守る。
- パッケージ間の依存関係によって並列実行が安全でなくなる場合は、ビルドの同時実行数を制限する。
### 変更のワークフロー
- 編集やコマンド実行の前に、変更を所有するパッケージを特定する。
- まず対象パッケージをビルドしてテストし、その後で共有契約に対する、より広い検証を行う。
- 生成済みバンドルではなくソースを編集し、指定されたパイプラインで再ビルドする。
- 一時的なランタイムの上書き設定とデバッガー設定は、ローカル環境内に留める。
- 生成物の変更が意図したものであることを確認してから、スナップショットを更新する。
## スナップショットと、全部の言語が通す共通のテストで動きを確かめる
高速なパッケージ単位のテストに、生成結果の回帰チェックと言語横断の適合性スイートを組み合わせる。
### 検証の層
- パッケージのロジックとプロトコルの境界ケースには、単体テストを使用する。
- 生成されたソースの意図しない変更を検出するために、スナップショットを使用する。
- クラス、インターフェース、データ構造、列挙型、コレクション、非同期呼び出し、オーバーライドを実行する共有フィクスチャを使用する。
- すべての言語バインディングで振る舞いが同等であることを検証するために、適合性テストを使用する。
### フィクスチャとレポート
- 実際の利用者が使うものと同じコンパイラーで、共有フィクスチャをコンパイルする。
- フィクスチャを変更した場合は、影響を受けるすべてのジェネレーター、リフレクションツール、ランタイムのスナップショットを更新する。
- 各適合性ケースは一度だけ定義し、すべての言語バインディングに、その実装または明示的な分類を要求する。
- ケースまたは結果が変わるたびに、適合性レポートを再生成してコミットする。
- バインディングがスイート定義にないケースを追加した場合や、レポートが最新でない場合は、検証を失敗させる。
## スナップショットとレポートをそろえて変更を出し、強制プッシュはしない
生成コード、プロトコル形式、ランタイムライブラリ、レポートを、連携して扱うリリース対象とみなす。
### 妥協できない制約
- すべてのターゲットで検証せずに、公開型の制約を緩めない。
- ホストランタイムを連携して更新せずに、プロトコル操作やタグ付きシリアライズ形式を変更しない。
- スナップショットを更新してレビューせずに、生成パターンを変更しない。
- ランタイムライブラリが意図的にシングルスレッドである場合に、スレッドセーフだと仮定しない。
- ビルドグラフが循環を拒否する場合に、パッケージ間の循環依存を導入しない。
- 同梱された出力を直接変更しない。
### プルリクエストと引き継ぎ
- リポジトリで必須とされるタイトル形式、スコープ規則、貢献ライセンスへの同意確認に従う。
- ソース変更に必要な生成済みスナップショットと適合性レポートをすべて含める。
- 無関係な生成ファイル、ロックファイルの変更、ローカル設定を変更対象に含めない。
- リモートブランチが進んでいる場合は、取得してリベースまたはマージし、競合を解消して検証し、通常どおりプッシュする。強制プッシュは決して行わない。
- 下流リポジトリや文書との同期は、記憶に頼らずリリースチェックリストに記録する。
