チーム協業 FAQ:契約、設定、フィクスチャ
要点:命名スタイル、時刻形式、null の意味論、enum の形、設定形式、フィクスチャの整理、マージ競合。チームが実際に議論する十二のトレードオフです。

要点:これらはチームの取り決めで一度だけ決めましょう。{"data":…,"meta":…} エンベロープと HTTP ステータスコード、サイト全体で一つの命名スタイル、タイムゾーン付き RFC 3339 タイムスタンプ、文字列 enum。その後、新しい API はフィールドを追加するだけで、変更も削除もしない同じ契約に沿って進化させます。
これらの質問に唯一の正解はありませんが、チームには一つの答えが必要です。さもないと新しい API のたびに同じ議論が始まります。
1. API 契約
API はどんな構造を返すべきか
よくある三つの形で、それぞれコストがあります。
| 形 | 例 | 利点 | 欠点 |
|---|---|---|---|
| 生データ | [{...}, {...}] |
単純 | メタデータを運べない |
| 固定エンベロープ | {"code":0,"message":"ok","data":{...}} |
エラー処理が一律 | 業務コードが HTTP ステータスを重複させる |
| リソースエンベロープ | {"data":{...},"meta":{...},"links":{...}} |
拡張可能、明確な意味論 | ネストが一段増える |
内部の業務 API では「リソースエンベロープ + 結果を表す HTTP ステータスコード」を優先します。
{
"data": [{ "id": "1", "name": "例" }],
"meta": { "total": 128, "page": 2, "pageSize": 20 }
}
エラーには、別の code をネストするのではなく、HTTP ステータスコードと構造化エラーボディを使います。
{
"error": {
"type": "validation_failed",
"message": "リクエストパラメータの検証に失敗しました",
"details": [
{ "path": "/email", "reason": "形式が正しくありません" }
]
}
}
要点:HTTP ステータスコードがすでに成功か失敗かを表すので、業務コードは冗長です。二つの体系が共存すると、 「201 なのに code が 500」のような不整合がほぼ必然的に生まれます。
裸の配列は避けてください。トップレベルが配列だとページネーション、総件数、カーソルを付けられず、後から足すにはすべての呼び出し元を変えなければなりません。
フィールド名は snake_case か camelCase か
一つ選び、サイト全体に適用し、一つの JSON 文書内で混ぜないでください。
| スタイル | 一般的なエコシステム | 例 |
|---|---|---|
camelCase |
JavaScript / TypeScript / Java | userName、createdAt |
snake_case |
Python / Ruby / Go(一部)/ データベース列 | user_name、created_at |
kebab-case |
稀。ハイフン付きキーはアクセスしにくい | user-name |
基準:
- この JSON を誰が消費するか:フロントエンドが主なら camelCase、データパイプラインと Python サービスが主なら snake_case。
- データベース列に直接対応するか:テーブル構造をそのまま通すなら snake_case が変換層を省きます。
- 既存システムの継続性:既存 API が使うものを保ちます。一貫性が正しさに勝ります。
どうしても変換が必要なら、業務コードに toCamelCase を散らすのではなく、境界(ゲートウェイかフロントエンドのリクエスト層)で行ってください。
時刻フィールドはどの形式を使うべきか
タイムゾーンオフセット付きの RFC 3339 / ISO 8601 文字列を使います。
{
"createdAt": "2026-10-30T11:37:57+08:00",
"updatedAt": "2026-10-30T03:37:57Z"
}
重要な点:
- タイムゾーンを含める:ゾーンなしの
2026-10-30 11:37:57はタイムゾーンをまたぐ場面で必ず壊れます。 Zかオフセットを一貫して使う:どちらも正しいですが、一つの API 内で混ぜないでください。- 数値タイムスタンプを避ける:秒かミリ秒か、うるう秒を含むか、チームごとに再解釈が必要です。 コンパクトさが必要ならミリ秒整数を使い、単位をドキュメントに固定します。
createdAt / updatedAt / deletedAt のように At 接尾辞のフィールド名を優先し、
値が期間ではなく時刻点であることが一目で分かるようにします。期間には timeoutMs、ttlSeconds を使い、単位を名前に入れます。
null は null かフィールド省略か
「このフィールドに意味があるか」に依存します。
| 意味論 | 形 | 備考 |
|---|---|---|
| フィールドは存在するがまだ値がない | "name": null |
「確認したが空だった」と明示する |
| フィールドがこのリソースと無関係 | 省略 | たとえば一般ユーザーに vipExpireAt は不要 |
| 部分更新(PATCH) | 省略 = 変更しない | ここでは null だけが「クリア」を意味する |
三番目は最も見落とされます。PATCH リクエストで
{ "nickname": null }
は「ニックネームをクリアする」を意味し、リクエストボディから nickname を省くのは「ニックネームに触れない」を意味します。
両方を「クリア」と扱うと、メールを変えたユーザーがニックネームを消してしまいます。
もう一度:undefined を使わないでください。正しい JSON 値ではなく、JSON.stringify がキーを完全に落とすので、
振る舞いは「省略」と一致するのにコードは「明示的に空の値を渡した」ように見えます。
真偽値フィールドはどう命名すべきか
is / has / can / should 接頭辞を使い、否定を避けます。
{
"isActive": true,
"hasPassword": false,
"canEdit": true
}
三つの教訓:
- 否定を使わない:
isDisabled: falseは読み手に一度反転させる。isEnabled: trueの方が直接的です。 二重否定(isNotDisabled)は災難です。 - 真偽値に数値を使わない:
"enabled": 1はすべての消費者に=== 1を書かせます。 - 文字列を使わない:
"enable": "true"は JavaScript で真値で、if (data.enable)は"false"でも真になります。そのようなバグは追跡が極めて難しいです。
enum フィールドは数値か文字列か
明確なストレージや帯域の制約がない限り、文字列を使います。
{ "status": "active" }
{ "status": 1 } と比べて文字列が勝つ理由:
- ログ、パケットキャプチャ、データベースがマッピング表を参照せず直接読める。
- enum 値を追加しても既存値の意味が変わらない(数値 enum の途中に挿入すると全部ずれる)。
- フロントエンドが
1 → 'active'のマッピングを同期して保守する必要がない。
コストは数バイトで、圧縮後はほとんど無視できます。
併せて:文字列 enum 値は小文字+アンダースコア(pending_review)にし、Schema の enum で制約して
不正な値を検証段階で拒否します。
2. 設定とフィクスチャ
設定は JSON、YAML、TOML のどれか
好みではなく目的で選びます。
| 形式 | 向く場面 | 向かない場面 |
|---|---|---|
| JSON | プログラムによる読み書き、厳格な構造データ、Schema 検証 | 頻繁な手編集(コメント不可) |
| YAML | 深くネストしたデプロイ設定(K8s) | 手書きのインデントは誤りやすく、yes/no が真偽値になる |
| TOML | 平坦な依存・ツール設定(Cargo.toml、pyproject.toml) |
深いネストの可読性が悪い |
| JSONC | コメントが必要なエディタ設定(tsconfig.json) |
システム境界を越える |
チームでよくある間違いは業務設定を YAML に保存することです。YAML の暗黙型変換(1.0 が数値、
NO が真偽値、2026-10-30 が日付)はパーサー間で異なる結果を生みます。
設定をプログラムで厳格に検証する必要があるなら、JSON + Schema を選び、コメントは _comment フィールドか別ドキュメントで文書化します。
テストフィクスチャはどう整理すべきか
「一つのベースライン + 最小差分」で整理し、テストケースごとに巨大な JSON をコピーしません。
fixtures/
├─ users/
│ ├─ base.json # ベースラインのユーザーオブジェクト
│ ├─ no-email.json # email だけが欠けている
│ └─ invalid-age.json # age だけが範囲外
└─ fixtures.ts # 読み込みと浅いマージのユーティリティ関数
import baseUser from './users/base.json';
export function makeUser(overrides: Partial<User> = {}): User {
return { ...baseUser, ...overrides };
}
こうすれば新しいテストケースは差分フィールドだけを書き、ベースラインのフィールドを変えるとすべてのケースが自動で更新されます。
三つの規則:
- フィクスチャは Schema 検証を通らなければならない:ベースライン自体が正しいデータであるべきです。
- 本番の実データをエクスポートしない:電話番号、ID 番号、住所はすべて個人情報です。
- フィクスチャはバージョン管理に入れるが小さく保つ:数十 KB を超える単一ファイルは削るか生成すべきです。
JSON ファイルのマージ競合を減らすには
JSON は競合しやすいです。フォーマッタがファイル全体を一度に再整形するので、二人がそれぞれ一行変えるとファイル全体で競合します。
四つの戦術:
- 整形を統一し
.editorconfigに追加する:一貫したインデント、行末、末尾改行が無関係な差分を避けます。 - キーを並べ替える:アルファベット順に合意し、新しいフィールドが他人の挿入点と衝突しないようにします。
- ファイルを分割する:巨大な
config.jsonを領域別に分け、競合面を自然に縮めます。 - 「全部再整形」コミットを避ける:整形と業務変更を二つのコミットに分け、レビューを読みやすく保ちます。
チームがロックファイル(pnpm-lock.yaml、package-lock.json)を使うなら、手でマージせず再生成します。
git checkout --theirs pnpm-lock.yaml
pnpm install
バージョン互換性をどう保つか
原則は「追加する、変更しない、削除しない」です。
| 変更 | 互換性 | 方法 |
|---|---|---|
| 任意フィールドを追加 | 互換 | 追加する。消費者は未知フィールドを無視する |
| 必須フィールドを追加 | 非互換 | バージョンを上げるか、既定値を提供する |
| フィールドを削除 | 非互換 | まず非推奨とマークし、呼び出し元を見てから削除 |
| フィールドの型を変更 | 非互換 | 新しいフィールド名を足し、移行期間中は二重書き込み |
| enum の値集合を変更 | 場合による | 値の追加は互換、削除は非互換 |
支える仕組み:
- 消費者は「寛容なパース」を使う。未知フィールドを無視し、欠落には既定値を与える。
- サーバーは古いフィールドをしばらく保ち、レスポンスに
Deprecationヘッダーをヒントとして返す。 - Schema で新しいフィールドを
requiredにせず、非推奨フィールドにdeprecated: trueを付ける。
CI で契約をどう検証するか
Schema をコードの一部として扱い、コミットのたびに検証します。
# サンプルデータを Schema で検証する(ajv-cli を使用)
npx ajv-cli validate -s schemas/user.json -d "fixtures/users/*.json" --strict=false
CI で三つを実行します。
- Schema 自体の妥当性:
$schema宣言が正しいか、$refが解決するか。 - フィクスチャが Schema に一致するか:テストデータのドリフトを防ぐ。
- 契約が後方互換か:前バージョンと比較し、削除されたフィールドや変更された型を確認する。
三番目にはツールの支援(openapi-diff や json-schema-diff など)が必要です。
なければ少なくとも required 配列と properties キー集合の変化をスクリプトで比較してください。
3. クイックリファレンス
| 質問 | 推奨 |
|---|---|
| レスポンス構造 | {"data":..., "meta":...}。エラーは HTTP ステータスコード |
| 命名スタイル | camelCase(フロントエンド優先)か snake_case(データ優先)。サイト全体で一貫 |
| 時刻形式 | タイムゾーン付き RFC 3339 文字列、At で終わるフィールド名 |
| null | 値なしは null、無関係なフィールドは省略、PATCH の省略は「変更しない」 |
| 真偽値命名 | is/has/can 接頭辞、否定なし、0/1 なし |
| enum | 文字列、小文字とアンダースコア、Schema に enum を追加 |
| 設定形式 | プログラム検証は JSON、手編集は TOML か JSONC |
| フィクスチャ | 一つのベースライン + 最小差分の上書き。Schema に適合 |
| マージ競合 | 整形統一、キー並べ替え、ファイル分割 |
| バージョン互換性 | 追加する、変更しない、削除しない。削除前に非推奨をマーク |