14 人が閲覧(直近 30 日) API

Google API設計ガイドとAIPとは|主要規約の要点とapi-linterでの準拠チェック

Google API設計ガイドとAIPとは|主要規約の要点とapi-linterでの準拠チェック

Google API設計ガイド(Cloud API Design Guide)は、GoogleがCloud APIを設計するときに従っている規約集です。ただし2024年10月の改訂で個別の設計ページはAIP(API Improvement Proposals)へ転送されるようになり、いま規約の本文を読むならAIPが正本です。本記事では、同じ「Google」の名前で出てくる3種類の資料の見分け方、AIPの番号とステータスの読み方を押さえたうえで、標準メソッド・命名・ページネーション・フィルタ・エラー・バージョニングの規約をAIPの原文に沿って整理します。後半ではapi-linter 2.4.0で自作のprotoを検査した実行結果と、OpenAPIで設計するチームが規約を取り入れる方法を載せます。

まとめ:Google API設計ガイドとAIPを読むときに押さえる要点

  • 「Google APIのガイド」には、APIを作る側のAPI design guide、APIを呼ぶ側のGoogle Cloud APIsの利用ドキュメント、Apigeeの電子書籍『Web API Design: The Missing Link』の3系統がある。本記事は1つ目を扱う。
  • ガイドの個別ページは2024年10月から、命名規則のページも2025年6月からAIPへ転送されている。AIPは100〜999番が汎用の設計指針で、状態が「Approved」のものが現行の規約。
  • 設計の順番はAIP-121が示すとおり「リソース(名詞)→階層→スキーマ→メソッド(動詞)」。メソッドはGet・List・Create・Update・Deleteの標準メソッドに寄せ、合わないものだけを :archive のようなカスタムメソッドにする。
  • UpdateはPATCHが基本。PUTによる全置換は、フィールド追加が後方互換性を壊すためAIP-134が強く非推奨としている。
  • 一覧取得は page_size/page_token/next_page_token の3フィールドで組み、トークンは利用者が解読できない不透明な文字列にする(AIP-158)。絞り込みは filter、並び替えは order_by の文字列1本ずつで受ける。filterでは OR が AND より先に結合する(AIP-160)。
  • 時刻は google.protobuf.Timestamp 型で create_time のように「動詞の原形+_time」で名付ける。created_time はapi-linterが違反として検出する(AIP-142)。
  • エラーは google.rpc.Status と正規のエラーコードで返し、ErrorInfo の reason は63文字以内のUPPER_SNAKE_CASEにする(AIP-193)。版は v1 のようなメジャー版だけを出し、alpha・betaはチャネル方式で v1beta と付ける(AIP-185)。
  • protoでAPIを定義しているなら、api-linterをCIに入れる価値がある。インストールは /v2/ 付きのパスで行い、CIでは --set-exit-status を必ず付ける。OpenAPIだけのチームは、PATCH+更新マスクなど4つの規約に絞って取り入れる。

「google api guide」「best practices」で出てくる3種類の資料の見分け方

API設計ガイドとCloud APIs利用ドキュメントの対象読者の違い

英語で「google api guide」と検索すると、目的の違う公式ドキュメントが混ざって表示されます。1つは本記事の主題であるAPI design guideです。自社でAPIを作る人に向けて、リソースの切り方やメソッドの命名を定めています。もう1つはGoogle Cloud APIsの概要ページで、Google CloudのAPIを呼び出す人に向けた入口です。

後者には、Cloud APIsはJSON HTTPのインターフェースをすべて備え、多くはgRPCも持つこと、TLSで暗号化した要求しか受け付けないこと、クォータがあることなどが書かれています。APIキーやOAuthの設定方法を知りたいなら、こちらから認証のドキュメントへ進みます。

観点 API design guide(AIP) Google Cloud APIs Apigee電子書籍
読者 APIを設計・実装する人 Google CloudのAPIを呼ぶ人 Web APIを設計する人
主な内容 命名・メソッド・エラー・版管理 認証・ライブラリ・クォータ URL設計・エラー・版管理
前提 protoとHTTPマッピング 呼び出しコードと設定 pragmatic REST

設計ガイドと利用ドキュメントは無関係ではありません。Google CloudのAPI自体がAIPに沿って作られているので、呼び出す側から見える形がそのまま規約の実例になります。このあとのgcloudとcurlの手順で、実在のAPIを呼んでそれを確かめます。

「google api best practices」で出るApigee電子書籍とAIPの違い

「google api best practices」で上位に出やすいのは、Google CloudのAPI管理製品Apigeeのチームが配布している電子書籍『Web API Design: The Missing Link』です。配布ページは「15年以上にわたりApigeeで大規模なAPIを設計・管理してきた経験」をまとめたものと説明し、最も重要な原則に「アプリ開発者の視点を最初に考えること」を挙げています。中身として示されているのは、pragmatic RESTの考え方、URL設計、エラー処理とバージョニングの指針、良い設計の例です。入手には氏名や会社のメールアドレスなどを登録するフォームの入力が必要です。

同じGoogleの資料でも、AIPとは性格が違います。AIPはprotoで定義したAPIをgRPCとREST/JSONの両方で公開する前提の規約で、api-linterで機械的に検査できます。電子書籍はHTTPで公開するWeb API全般の設計の考え方を示す読み物です。社内の規約として番号付きで参照し、CIで守らせたいならAIP、REST APIの設計方針を一から考えるための入門なら電子書籍、と使い分けるのが筋です。

gcloudとcurlで実在のGoogle APIを呼んで規約を確かめる手順

Pub/SubのREST APIを例にします。gcloud services enableでAPIを有効にし、gcloud auth print-access-tokenで取得したアクセストークンをAuthorizationヘッダーに載せれば、curlから直接呼べます。my-project と my-topic は自分の環境の値に置き換えてください。

# APIを有効化し、アクセストークンを取得する
PROJECT=my-project
gcloud services enable pubsub.googleapis.com --project="${PROJECT}"
TOKEN=$(gcloud auth print-access-token)

# 標準メソッドList:GETで、ページングはクエリのpageSizeとpageToken
curl -s -H "Authorization: Bearer ${TOKEN}" \
  "https://pubsub.googleapis.com/v1/projects/${PROJECT}/topics?pageSize=2"

# カスタムメソッド:publish:URI末尾がコロン+動詞で、副作用があるのでPOST
curl -s -X POST \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{"messages":[{"data":"aGVsbG8="}]}' \
  "https://pubsub.googleapis.com/v1/projects/${PROJECT}/topics/my-topic:publish"

projects.topics.listのリファレンスによると、一覧のクエリパラメータは pageSize と pageToken で、レスポンスは topics[] と nextPageToken です。nextPageToken が空でなければ続きがあり、その値を次の要求の pageToken に渡します。後述するAIP-158の3フィールドが、JSONではcamelCaseになって現れているのが分かります。

projects.topics.publishのリファレンスでは、要求の行が POST https://pubsub.googleapis.com/v1/{topic}:publish と定義され、成功時は messageIds[] が返ります。トピックが無ければ NOT_FOUND です。上のコマンドは手元で実行した結果を載せたものではないため、実際の応答は各自の環境で確認してください。

Google API設計ガイドとAIPの関係と2024年10月以降の正本の所在

2014年からGoogle社内で使われてきた設計ガイドの位置づけと適用範囲

ガイドの冒頭には、ネットワークAPI向けの一般的な設計ガイドであり「2014年以来Google内部で使用され、Cloud APIやその他のGoogle APIを設計するときにGoogleが従うガイド」だと書かれています。対象はREST APIとRPC APIの両方で、特にgRPC APIに重点を置いています。gRPC APIはProtocol BuffersでAPIの表面を定義し、HTTPマッピングによってJSON/HTTPからも呼べるようにする、というのが前提の構成です。gRPCそのものの仕組みはgRPCとは?仕組み・RESTとの違い・UDP/TCPの扱いを実装例つきで解説で、protoの書き方はProtocol Buffersとは?protoファイルの書き方と採用判断を実装者向けに解説で扱っています。

見落とされやすいのが、ガイドの使用は必須ではないと明記されている点です。Cloud EndpointsやgRPCはガイドに従わなくても使えます。「Googleのガイドだから従うべき規格」ではなく、protoを起点に多数のAPIを揃えるための社内規約を外部に共有したもの、と捉えるのが正確です。

2024年10月以降に個別ページがAIPへ転送された経緯と対応表

ガイドの変更履歴には、2024年10月に「Directory Structure と Naming Conventions を除く設計ページをGoogle AIPへリダイレクトした」、2025年6月に「Naming Convention のページもAIPへリダイレクトした」と記録されています。旧URLの「標準メソッド」ページを開くと、AIP-130(Methods)の内容が返ります。ガイドの日本語版は概要ページこそ残っていますが、個別の規約は英語のAIPで読む形です。

AIP-1によると、AIPはGoogleにおけるAPI関連ドキュメントの信頼できる情報源(source of truth)であり、APIチームが指針について合意を形成する手段でもあります。AIP-1の作成日は2018年8月20日です。汎用の指針をまとめた一覧(google.aip.dev/general)には、2026年10月17日時点で73件が並んでいます。ガイドの目次がどのAIPに対応するかは次のとおりです。

論点 参照するAIP
リソース指向設計 AIP-121
リソース名 AIP-122
HTTPとgRPCの変換 AIP-127
標準メソッド5種 AIP-131〜135
カスタムメソッド AIP-136
フィールド名 AIP-140
長時間実行オペレーション AIP-151
ページネーション AIP-158
フィルタ AIP-160
フィールドマスク AIP-161
後方互換性 AIP-180
バージョニング AIP-185
エラー AIP-193
フィールドの振る舞い注釈 AIP-203
バッチ取得 AIP-231

表の番号は、ガイドの旧目次から引くための索引として使えます。AIPは個別に改訂されるので、引用するときは参照した日付も控えておくと後で差分を追えます。

AIPの番号ブロック・ステータスとMUST・SHOULDの読み方(AIP-1・2・8)

AIPの番号は内容と無関係に付くわけではありません。AIP-2は番号の範囲(ブロック)を次のように割り当てています。設計ガイドの後継として読むのは、100〜999番の汎用ブロックです。

番号 対象
1〜99 AIPの運用そのもの(メタ)
100〜999 汎用のAPI設計指針
2500〜2599 Google Cloud
2700〜2799 Google Workspace
3000〜3099 Actions on Google
3200〜3299 Firebase
4100〜4199 認証ライブラリ
4200〜4299 クライアントライブラリ
4600〜4699 Geo

各AIPのページ上部には状態(State)が表示されます。AIP-1が定める状態は、Draft・Reviewing・Approved・Withdrawn・Rejected・Deferred・Replacedの7つです。規約として扱ってよいのはApprovedで、AIP-1はこれを「best current practice」と位置づけています。Reviewingへ進めるには著者以外の承認者1人、Approvedへ進めるには2人の正式な承認が必要です。番号は承認前のAIPにも付くため、番号があることは採用済みの証拠になりません。

本文の読み方はAIP-8が決めています。要求の強さはRFC 2119の「must」「must not」「should」「should not」「may」で表し、AIPの中では小文字の太字で書かれます。shouldを使う場合は、ほかの事情が優先される例を添えることになっています。つまり太字のmustは例外なく守る規則、shouldは理由があれば外してよい規則です。自社APIへ取り入れるときは、まずmustだけを拾うと全体の負担が見積もれます。

リソース指向設計と標準メソッド5種をHTTPメソッドへ対応づける規則

リソース名とコレクションIDの命名規則(AIP-121・AIP-122)

AIP-121は、設計時に考える順番を「提供するリソース(名詞)」「リソース間の関係と階層」「各リソースのスキーマ」「各リソースが持つメソッド(動詞)」と定めています。リソース指向設計はRESTから多くの原則を借りつつ、独自のパターンも定義していると明記されています。RESTとの関係やURIへの落とし込みを掘り下げるならAPIエンドポイントとURLの違い|リソース指向アーキテクチャ(ROA)で決めるURI設計とRESTとの関係が詳しいです。

リソース名は publishers/123/books/les-miserables のように、コレクションID(publishers)とリソースID(123)を交互に並べます。AIP-122の規則は次の3点です。

  • コレクションIDはリソースを表す名詞の複数形にする(Publisherなら publishers)
  • コレクションIDは簡潔なアメリカ英語で、小文字始まりのcamelCaseにする
  • 区切りは / で、末尾以外のセグメントに / を含めない

綴りはAIP-190が「licenceではなくlicense、colourではなくcolor」とアメリカ英語を指定しています。先ほどのPub/Subの projects/{project}/topics/{topic} も、この並びに従っています。

標準メソッド5種とHTTPメソッドの対応表とPATCHを選ぶ理由

標準メソッドは、RESTで慣習になっているHTTPリクエストをRPCとして定義し直したものです。各AIPが示す対応は次の表のとおりです。

メソッド HTTP URIの対象 AIP
Get GET 個別リソース(name) 131
List GET コレクション(parent) 132
Create POST コレクション(parent) 133
Update PATCH 個別リソース(book.name) 134
Delete DELETE 個別リソース(name) 135

URIの具体形は、個別リソースを指すGet・Deleteが /v1/{name=publishers/*/books/*}、コレクションを指すList・Createが /v1/{parent=publishers/*}/books です。Updateだけは本文のリソースのフィールドを変数に使い、/v1/{book.name=publishers/*/books/*} と書きます。

一般的なREST設計の解説と最も食い違うのがUpdateです。AIP-134は、Updateメソッドが「RESTのPATCHの振る舞いを反映する」と定め、対象フィールドは update_mask(google.protobuf.FieldMask)で指定させます。全置換しかしないならPUTも使えますが、リソースへのフィールド追加が後方互換性のない変更になってしまうため「強く非推奨(strongly discouraged)」とされています。同期Updateのレスポンスは更新後のリソースそのものです。長時間実行の場合は、そのリソースを最終結果に持つ google.longrunning.Operation を返します。いずれの場合も、UpdateBookResponse のような専用メッセージは作りません。

更新マスクの書き方はAIP-161が定めています。マスクは常にリソースからの相対パスで書き(book.title ではなく title)、入れ子のフィールドは . でたどります。繰り返しフィールドの要素を添字で指すことはできません。汎用のREST APIでPUTとPATCHをどう使い分けるかはREST API設計のベストプラクティス|URI・冪等性・エラー応答のレビュー基準で扱っています。

カスタムメソッドのコロン記法とバッチ取得の書き方(AIP-136)

「アーカイブする」「キャンセルする」のように標準メソッドで表せない操作は、URIの末尾にコロンと動詞を付けたカスタムメソッドにします。AIP-136の規則は具体的です。

  • URIは : の後にカスタム動詞を続ける(例:/v1/{name=publishers/*/books/*}:archive)
  • HTTPメソッドはGETかPOSTのどちらか。副作用がある操作はPOST必須で、データ取得でもペイロードがURLの長さ制限を超えるならPOSTを使ってよい
  • メソッド名は「動詞+名詞」で、for・withなどの前置詞を含めない
  • 名前の動詞部分には、原則としてGet・List・Create・Update・Deleteを含めないことが推奨される。Async も付けず、長時間実行版と区別したいときは接尾辞 LongRunning を使う

複数リソースをまとめて取得するバッチメソッドも同じ記法で、AIP-231はURIが :batchGet で終わりHTTPメソッドはGETと定めています。時間のかかる処理は、AIP-151に従い最終結果の代わりに google.longrunning.Operation を返します。どれくらいから長時間とみなすかの目安は10秒です。メソッドには google.longrunning.operation_info 注釈を付けて結果の型とメタデータの型を両方示し、Operationの定義をAPIごとに複製したり独自の進捗確認インターフェースを作ったりはしません。並行操作を受け付けないリソースに2つ目の操作が来たら、ABORTED を返します。Pub/Subの :publish は、メッセージを送るという副作用があるためPOSTになっている実例です。

フィールド命名・時刻・ページネーション・フィルタをprotoで定義する規約

lower_snake_caseのproto定義とJSONキーへの変換規則

AIP-140は、protoファイルのフィールド定義を lower_snake_case で書き、JSONや生成コードではそれぞれに適した命名規則へ変換されるとしています。単語を数字で始めることは禁止です。スネークケースとキャメルケースの変換で曖昧になるためです。先頭・末尾・連続のアンダースコアも使えません。

変換先の具体的な規則は、Protocol BuffersのProtoJSON Formatに書かれています。メッセージのフィールド名はJSONのキーとしてlowerCamelCaseに変換され、パーサーは変換後の名前と元のproto名の両方を受け付けなければなりません。FieldMask はJSONでは "f.fooBar,h" のようなカンマ区切りの文字列です。さらにAIP-127は、URIにも本文にも入らないフィールドをクエリパラメータで受け、クライアントはフィールド名をcamelCaseで送ると定めています。protoの page_token がPub/Subのクエリで pageToken になっていたのは、この規則によるものです。

field_behavior注釈で必須・任意・出力専用を示す3つの値

Google方式に特有なのがAIP-203の google.api.field_behavior 注釈です。リクエストで使うメッセージのフィールドには原則として付けます。ただし、AIP-154のリソースのetagフィールドは付けないことが推奨され、oneofフィールドは省略してもかまいません。以下はこれらの例外を除く規則で、最低でも REQUIRED・OPTIONAL・OUTPUT_ONLY のいずれかを使います。

注釈はproto自体の動作には影響しません。それでもCLIやSDKがこの注釈を頼りにコードを生成するため、付け忘れはクライアントの品質に直結する問題です。注釈が無いフィールドは後方互換のために OPTIONAL と解釈されますが、注釈の省略そのものは禁止されています。リソース名のフィールドには IDENTIFIER を使い、後述のapi-linterもこの付け忘れを検出します。

create_timeなどの標準フィールドと時刻の名付け方(AIP-148・AIP-142)

どのリソースにも現れる概念は、AIP-148が名前と意味を固定しています。主なものは次のとおりです。

  • name:リソース名。すべてのリソースが持ち、最初のフィールドに置くのが推奨
  • display_name:画面表示用の名前。利用者が変更でき、一意である必要はなく63文字以内が推奨
  • create_time・update_time・delete_time:出力専用(OUTPUT_ONLY)の作成・更新・論理削除の時刻
  • uid:システムが振る出力専用の一意識別子。持つならUUID4とし、google.api.field_info の format に UUID4 を指定する
  • given_name・family_name:人名。名が先とは限らない文化があるため first_name・last_name は使わない

時刻の型と名前はAIP-142です。ある時点は google.protobuf.Timestamp、時間の長さは google.protobuf.Duration、暦の日付は google.type.Date を使います。時点のフィールド名は _time で終え、出来事を表すときは動詞の原形を前に置きます。本を出版した時刻なら publish_time です。published_time・created_time・last_updated_time のような過去形は使いません。既存の仕様の都合で整数の時刻を使う場合は、send_time_millis のように単位を末尾に付けます。

page_tokenを使うページネーションの設計(AIP-158・AIP-132)

AIP-158の一覧取得は、オフセットではなくトークンで次のページを指す方式です。

  • リクエストに int32 page_size と string page_token を置く。どちらも必須にしない
  • page_size が未指定か0なら、APIが適切な既定値を選んでドキュメントに書く。エラーにはしない。上限を超えた値は上限に丸め、負の値には INVALID_ARGUMENT を返す(AIP-132)
  • レスポンスの next_page_token が省略されていれば、それ以上のページは無い
  • コレクションの末尾でなくても、要求より少ない件数(0件を含む)を返してよい
  • 総件数の total_size は任意で、推定値でもよい(推定ならそう明記する)

トークンは、URLセーフかつ利用者が解読できない不透明な文字列でなければなりません。AIP-158は理由を「解読できれば利用者は解読する」と書いています。ページングの実装詳細がAPIの一部になり、壊さずに変えられなくなるからです。透明なトークンをBase64エンコードしただけでは不十分だという警告も付いています。オフセット方式とカーソル方式の選び方はページネーション設計とは?オフセットとカーソルの選び分けと実装を解説を参照してください。

filterの構文とorder_byの並び順指定(AIP-160・AIP-132)

絞り込みはAIP-160が扱います。APIごとに専用の構造体を作るのではなく、構造化された構文を持つ文字列フィールド1本(filter)で受ける方針です。フィルタの要件は頻繁に変わるため、文字列にしておけばUIやクライアントの更新を待たずに条件を増やせる、というのが理由です。構文はEBNFで定義されています。

filter = "author = \"Victor Hugo\" AND (publish_time >= \"2020-01-01T00:00:00Z\" OR rating > 4)"
filter = "NOT genre = \"poetry\""
filter = "title = \"*Miserables\""
order_by = "publish_time desc, title"

比較演算子は =・!=・<・>・<=・>=、否定は NOT と - の両方、入れ子のフィールドは . でたどります。フィールド名は必ず左辺に置き、時刻はRFC 3339形式の文字列、時間の長さは 20s のように秒の接尾辞付きで書きます。文字列の等価比較では * のワイルドカードを使えるようにすることが推奨されています。

実装で最も間違えやすいのが優先順位です。AIP-160では、多くのプログラミング言語と逆に OR が AND より強く結び付きます。a AND b OR c は a AND (b OR c) と評価されます。話し言葉に合わせた設計で、AIP-160はドキュメントや例で括弧を明示するよう勧めています。SQLの感覚でパーサーを書くと、同じ文字列でも結果が変わるので注意してください。

並び替えはAIP-132の order_by 1本で受けます。値はカンマ区切りのフィールド名で既定は昇順、降順にするフィールドだけ後ろに " desc" を付けます。余分な空白は無視されるので、"foo, bar desc" と "foo,bar desc" は同じ意味です。AIP-132は、filterもorder_byも必要が確かになってから足すよう求めています。後から足すのは互換性を壊しませんが、一度出したものを外すと破壊的変更になるためです。sort_order のような独自名のフィールドを置くと、後述のapi-linterが違反として検出します。

エラー応答の形式とバージョニングで後方互換性を守るための3つの規約

google.rpc.StatusとErrorInfoで返すエラー応答の形式

AIP-193は、エラー時に google.rpc.Status を返し、google.rpc.Code で定義された正規のエラーコードを使うことを必須としています。HTTP/JSONで返す場合、コード5(NOT_FOUND)は次の形になります。

{
  "error": {
    "code": 404,
    "message": "The requested book was not found.",
    "status": "NOT_FOUND",
    "details": [{
      "@type": "type.googleapis.com/google.rpc.ErrorInfo",
      "reason": "BOOK_NOT_FOUND",
      "domain": "library.example.com"
    }]
  }
}

AIP-193は、すべてのエラーレスポンスの details に ErrorInfo を含めることも必須にしています。エラーの識別には reason と domain の組を使います。domain はエラーを出したサービス名(pubsub.googleapis.com など)のような世界で一意な値です。reason はそのdomainの中で一意な値で、63文字以内、正規表現 [A-Z][A-Z0-9_]+[A-Z0-9] に合うUPPER_SNAKE_CASEと決められています。AIPが挙げる良い例は CPU_AVAILABILITY、NO_STOCK などです。ErrorInfo が無いとクライアントは Status.message の文面を解析するしかなくなり、その文面が暗黙の契約になって変更できなくなる、というのがAIP-193の説明です。16種のコードの意味とHTTPステータスの対応はgRPCのステータスコードとは?16種の意味・HTTP対応表・リトライ可否を解説にまとめています。

メジャー版だけを公開するv1方式とalpha・betaのチャネル運用

AIP-185は、すべてのGoogle APIインターフェースにメジャー版番号を持たせ、protobufのパッケージ名の末尾とREST APIのURIパスの先頭に入れるよう定めています。セマンティックバージョニングと違い、マイナー版・パッチ版の番号は公開しません。v1.0 や v1.4.2 ではなく v1 です。互換性のある変更はそのメジャー版の中で「その場で」反映され、利用者は移行作業なしに新機能を受け取ります。

AIP-185にはバージョニングの戦略が4つ並記されていて、推奨はチャネル方式(Channel-based versioning)です。4つすべてがalpha・betaの付け方の違いというわけではありません。たとえばインターフェース方式(Interface-based versioning)では、リクエストごとに X-Goog-Api-Version ヘッダーか $apiVersion クエリパラメータで版を指定します。リリース方式(Release-based versioning)は、新しいサービスではあまり使われないと明記されています。推奨のチャネル方式の規則は次のとおりです。

  • メジャー版ごとにalpha・beta・stableの最大3チャネルを持つ
  • stableは v1、betaは v1beta、alphaは v1alpha。stableに v1beta を名乗らせたり、betaに v1 を使ったりはしない
  • betaの機能はstableの上位集合、alphaはbetaの上位集合でなければならない
  • 非推奨にした要素を上位チャネルへ昇格させない

同じAPIの異なるメジャー版は、移行期間中に1つのクライアントアプリから同時に使えなければなりません。旧版の停止には、十分に周知した非推奨期間を置きます。版番号の付け方を一般論から整理するならバージョニングとは?セマンティック/APIバージョニングの意味と運用設計を実装者向けに解説【2026年】が向いています。

後方互換性の3つの観点とページネーションを後付けするときの落とし穴

AIP-180は互換性を、ソース互換(旧版向けのコードが新版でコンパイル・実行できる)、ワイヤ互換(旧版向けのコードが新しいサーバーと正しく通信できる)、セマンティック互換(利用者が期待する結果を受け取り続ける)の3つに分けています。同じメジャー版の中では、次の変更はしてはいけません。

  • 既存のリクエストメッセージやリソースに、新しい必須フィールドを足す
  • リソースのフィールドの既定値を変える

AIP-180が挙げる落とし穴が、ページネーションの後付けです。これまで全件を返していたListに page_size を足し、既定値を従来の件数より小さくすると、古いクライアントは全件を受け取ったと誤解します。一方で、呼び出し元が同じチームだけに限られるAPIは、自分たちの互換性要件を慎重に検討すべきだとも書かれています。全APIに同じ厳しさを求めているわけではありません。

api-linterでprotoのAIP準拠を機械チェックしてCIに組み込む手順

api-linter v2系のインストールとimport用protoの配置

api-linterは、protoファイルがAIPに沿っているかを検査するGoogle公式のツールです。リリース一覧では、2026年10月17日時点の最新が2026年9月10日公開のv2.4.0です。以下ではv2.4.0を、macOS(Go 1.26.5、2026年9月14日)で実行した結果を示します。v2.0.0(2025年10月22日)で内部がprotobuf-goへ移行しており、それに伴ってGoモジュールのパスが変わりました。

go install github.com/googleapis/api-linter/v2/cmd/api-linter@latest
api-linter --version

上は最新のv2系を入れる書き方です。版を固定するときは、末尾の「@latest」を「@v2.4.0」のようなタグ指定に替えます。/v2 を付けずに github.com/googleapis/api-linter/cmd/api-linter@latest を指定すると、エラーにはならず旧系列の1.72.0が入ります。api-linter --version で必ず確認してください。v2.4.0の --list-rules は342ルール(core 340、client-libraries 2)を返しました。

検査対象のprotoが google/api/annotations.proto などをimportしている場合、そのファイルが検索パス上に無いと open google/api/annotations.proto: no such file or directory で止まります。googleapisリポジトリの google/api ディレクトリを手元に置き、-I でパスを通します。

違反を4種類混ぜたprotoをapi-linterで検査した18ルールの検出結果

次のprotoファイル(example/library/v1/library.proto)は、ありがちな書き方を4つ混ぜています。フィールド名がcamelCase、ListRequestにページングのフィールドが無く独自の max_results がある、Updateが update_mask を持たずPUTを使っている、の4点です。

syntax = "proto3";

package example.library.v1;

import "google/api/annotations.proto";
import "google/api/field_behavior.proto";
import "google/api/resource.proto";

service LibraryService {
  rpc GetBook(GetBookRequest) returns (Book) {
    option (google.api.http) = {
      get: "/v1/{name=publishers/*/books/*}"
    };
  }

  rpc ListBooks(ListBooksRequest) returns (ListBooksResponse) {
    option (google.api.http) = {
      get: "/v1/{parent=publishers/*}/books"
    };
  }

  rpc UpdateBook(UpdateBookRequest) returns (Book) {
    option (google.api.http) = {
      put: "/v1/{book.name=publishers/*/books/*}"
      body: "book"
    };
  }
}

message Book {
  option (google.api.resource) = {
    type: "library.example.com/Book"
    pattern: "publishers/{publisher}/books/{book}"
  };
  string name = 1;
  string displayName = 2;
}

message GetBookRequest {
  string name = 1;
}

message ListBooksRequest {
  string parent = 1;
  int32 max_results = 2;
}

message ListBooksResponse {
  repeated Book books = 1;
}

message UpdateBookRequest {
  Book book = 1;
}

検証用なのでJavaのパッケージ指定(AIP-191)とコメント必須(AIP-192)のルールは設定ファイルで外しました。設定ファイルの書式は公式ドキュメントのYAML例に合わせています。

- included_paths:
    - "**/*.proto"
  disabled_rules:
    - "core::0191::java-package"
    - "core::0191::java-outer-classname"
    - "core::0191::java-multiple-files"
    - "core::0192::has-comments"
api-linter --config api-linter.yaml --output-format summary --set-exit-status example/library/v1/library.proto

作業ディレクトリに api-linter.yaml と上のprotoファイルを置き、googleapisリポジトリの google/api ディレクトリを同じ場所にコピーして実行しました。カレントディレクトリは常にimportの検索パスに含まれるため、この配置なら -I は不要です。出力は次のとおりです。

+--------------------------------------------+------------------+----------------+
|                    RULE                    | TOTAL VIOLATIONS | VIOLATED FILES |
+--------------------------------------------+------------------+----------------+
| core::0134::request-mask-required          |                1 |              1 |
| core::0132::request-parent-reference       |                1 |              1 |
| core::0140::lower-snake                    |                1 |              1 |
| core::0123::resource-singular              |                1 |              1 |
| core::0134::method-signature               |                1 |              1 |
| core::0132::request-unknown-fields         |                1 |              1 |
| core::0134::http-method                    |                1 |              1 |
| core::0132::request-parent-behavior        |                1 |              1 |
| core::0158::request-page-token-field       |                1 |              1 |
| core::0131::request-name-reference         |                1 |              1 |
| core::0131::method-signature               |                1 |              1 |
| core::0132::method-signature               |                1 |              1 |
| core::0131::request-name-behavior          |                1 |              1 |
| core::0158::request-page-size-field        |                1 |              1 |
| core::0203::resource-name-identifier       |                1 |              1 |
| core::0123::resource-plural                |                1 |              1 |
| core::0158::response-next-page-token-field |                1 |              1 |
| core::0203::field-behavior-required        |                5 |              1 |
+--------------------------------------------+------------------+----------------+
Linted 1 proto files
2026/09/14 21:55:56 found problems during linting

終了コードは1でした。混ぜた4点はそれぞれ 0140::lower-snake、0158 の3ルールと 0132::request-unknown-fields、0134::request-mask-required、0134::http-method として検出されています。加えて、method_signature や field_behavior の注釈漏れも拾われました。表の行の並び順は実行のたびに変わります。差分比較には --output-format json を使い、ファイルパスと各違反のルールID・行番号・列番号で並び順を正規化します。

修正後のprotoで違反0件を確かめた結果とリンターで検出できない範囲

検出結果に沿って直したファイル全体です。Updateは patch と update_mask、Listは page_size/page_token/next_page_token、リソースには singular/plural と IDENTIFIER 注釈を付けています。

syntax = "proto3";

package example.library.v1;

import "google/api/annotations.proto";
import "google/api/client.proto";
import "google/api/field_behavior.proto";
import "google/api/resource.proto";
import "google/protobuf/field_mask.proto";

service LibraryService {
  option (google.api.default_host) = "library.example.com";

  rpc GetBook(GetBookRequest) returns (Book) {
    option (google.api.http) = {
      get: "/v1/{name=publishers/*/books/*}"
    };
    option (google.api.method_signature) = "name";
  }

  rpc ListBooks(ListBooksRequest) returns (ListBooksResponse) {
    option (google.api.http) = {
      get: "/v1/{parent=publishers/*}/books"
    };
    option (google.api.method_signature) = "parent";
  }

  rpc UpdateBook(UpdateBookRequest) returns (Book) {
    option (google.api.http) = {
      patch: "/v1/{book.name=publishers/*/books/*}"
      body: "book"
    };
    option (google.api.method_signature) = "book,update_mask";
  }
}

message Book {
  option (google.api.resource) = {
    type: "library.example.com/Book"
    pattern: "publishers/{publisher}/books/{book}"
    singular: "book"
    plural: "books"
  };

  string name = 1 [(google.api.field_behavior) = IDENTIFIER];
  string display_name = 2 [(google.api.field_behavior) = OPTIONAL];
}

message GetBookRequest {
  string name = 1 [
    (google.api.field_behavior) = REQUIRED,
    (google.api.resource_reference).type = "library.example.com/Book"
  ];
}

message ListBooksRequest {
  string parent = 1 [
    (google.api.field_behavior) = REQUIRED,
    (google.api.resource_reference).child_type = "library.example.com/Book"
  ];
  int32 page_size = 2 [(google.api.field_behavior) = OPTIONAL];
  string page_token = 3 [(google.api.field_behavior) = OPTIONAL];
}

message ListBooksResponse {
  repeated Book books = 1;
  string next_page_token = 2;
}

message UpdateBookRequest {
  Book book = 1 [(google.api.field_behavior) = REQUIRED];
  google.protobuf.FieldMask update_mask = 2 [(google.api.field_behavior) = OPTIONAL];
}
+------+------------------+----------------+
| RULE | TOTAL VIOLATIONS | VIOLATED FILES |
+------+------------------+----------------+
+------+------------------+----------------+
Linted 1 proto files

終了コードは0でした。ただし、違反0件をもってAIP準拠とは言えません。公式ドキュメント自身が、AIPの指針すべてをルールで表現できるわけではなく、リンターは指針を読んで理解することの代わりにはならない、と注意しています。リソースの切り方やカスタムメソッドにすべきかどうかの判断は、ツールでは検出できません。

時刻フィールド名と並び替えフィールドの違反を検出した結果(2026年10月17日)

本記事で追加したAIP-142・AIP-148・AIP-132の規約も、api-linterで確かめました。環境はGo 1.27.2(macOS)、api-linterは同じv2.4.0です。サービスには ListBooks だけを定義し、Book と ListBooksRequest に次の4か所を仕込みました。過去形の created_time、注釈の無い update_time、単位の無い整数の時刻 publish_epoch、独自名の並び替えフィールド sort_order です。filter と order_by はAIPどおりの名前で置いています。

message Book {
  // google.api.resource の注釈は修正後のprotoと同じなので省略
  string name = 1 [(google.api.field_behavior) = IDENTIFIER];
  string display_name = 2 [(google.api.field_behavior) = OPTIONAL];
  google.protobuf.Timestamp created_time = 3 [(google.api.field_behavior) = OUTPUT_ONLY];
  google.protobuf.Timestamp update_time = 4;
  int64 publish_epoch = 5 [(google.api.field_behavior) = OPTIONAL];
}

message ListBooksRequest {
  // parent・page_size・page_token は修正後のprotoと同じなので省略
  string filter = 4 [(google.api.field_behavior) = OPTIONAL];
  string order_by = 5 [(google.api.field_behavior) = OPTIONAL];
  string sort_order = 6 [(google.api.field_behavior) = OPTIONAL];
}
+---------------------------------------+------------------+----------------+
|                 RULE                  | TOTAL VIOLATIONS | VIOLATED FILES |
+---------------------------------------+------------------+----------------+
| core::0121::resource-must-support-get |                1 |              1 |
| core::0142::time-field-names          |                1 |              1 |
| core::0148::field-behavior            |                1 |              1 |
| core::0132::request-unknown-fields    |                1 |              1 |
+---------------------------------------+------------------+----------------+
Linted 1 proto files

終了コードは1でした。--output-format yaml で位置を出すと、core::0142::time-field-names は created_time の行を指し、修正案として create_time を示しました。core::0148::field-behavior は「update_time には OUTPUT_ONLY を付けるべき」という指摘で、core::0132::request-unknown-fields は sort_order の行だけを指しています。filter と order_by は違反になっていません。resource-must-support-get は、検証のためGetメソッドを省いたことによるものです。

一方、単位の無い int64 publish_epoch は検出されませんでした。そこでAIP-142が互換用に示す名前の形に合わせ、int64 send_time_millis へ変えて再実行すると、今度は core::0142::time-field-type が「時刻を表すフィールドは google.protobuf.Timestamp を使うべき」と指摘しました。v2.4.0のソースを見ると、このルールはフィールド名の最後の語が time・millis・seconds などで、型がTimestamp系でないときに反応します。epoch はその語の一覧に無いので、すり抜けたわけです。

既存の仕様の都合で整数を残すと決めたフィールドは、そのフィールドの直前にルールを止めるコメントを書きます。理由も同じコメントに残しておくと、後からレビューする人が判断を追えます。

  // (-- api-linter: core::0142::time-field-type=disabled
  //     aip.dev/not-precedent: 既存仕様の都合でミリ秒の整数を使う --)
  int64 send_time_millis = 5 [(google.api.field_behavior) = OPTIONAL];

このコメントを付けて再実行すると、time-field-type の行だけが消えました。リンターが黙っていても時刻を整数で持たせていないかはレビューで見る必要があり、逆にリンターが指摘してもAIP自身が認める例外なら、コメントで理由を残して止めるのが正しい扱いです。

CIで違反を確実に失敗させる終了コードの設定と実行スクリプト

CIに組み込むときの注意点があります。--set-exit-status を付けず、さらに設定ファイルの指定も外して同じファイルを検査すると、22ルールで違反が検出されても終了コードは0でした。設定ファイルの指定を維持した場合は18ルールです。付け忘れると、パイプラインは違反を素通しします。

CIのジョブから呼ぶシェルスクリプトの例を示します。googleapisリポジトリを浅くクローンして検索パスに渡し、結果はJSONで出力します。

#!/usr/bin/env bash
# リポジトリ内のprotoをapi-linterで検査し、違反があればCIを失敗させる
set -euo pipefail

go install github.com/googleapis/api-linter/v2/cmd/[email protected]
api-linter --version

# google/api/*.proto をimportできるようにgoogleapisを取得する
git clone --depth 1 https://github.com/googleapis/googleapis.git "${RUNNER_TEMP:-/tmp}/googleapis"

api-linter \
  -I "${RUNNER_TEMP:-/tmp}/googleapis" \
  --config api-linter.yaml \
  --output-format json \
  --set-exit-status \
  $(git ls-files '*.proto')

2026年10月17日に同じファイルをJSON出力で3回検査すると、3回とも中身のハッシュ値が異なりました。JSONでも違反の配列の順序は揺れます。それでもファイルパス・ルールID・位置情報を取り出せるため、並び順を正規化すれば差分比較でき、プルリクエストへのコメント化にも使えます。版は検証済みのv2.4.0に固定しています。新しい版ではルールの追加や修正で、以前通ったファイルが違反になることがあるためです。版を上げるときは、検出結果の変化を確かめてから切り替えます。既存のAPIへ後から入れる場合は、いきなり全ルールで落とすと修正が追いつきません。設定ファイルの disabled_rules で一部を外して導入し、直した順にルールを戻していく進め方が現実的です。

OpenAPIでREST APIを設計するチームがAIPから取り入れる4つの規約

PATCHとupdateMaskと不透明なpageTokenをOpenAPIで書いた例

api-linterが検査できるのはprotoファイルだけです。それでも、AIPの規約の一部はOpenAPIの定義にそのまま写せます。次の例は、前の章の UpdateBook と ListBooks を、Google APIのJSON表現(クエリはcamelCase、更新マスクはカンマ区切りの文字列)に合わせて書いたものです。

openapi: 3.1.0
info:
  title: Library API
  version: v1
paths:
  /v1/publishers/{publisher}/books:
    get:
      operationId: ListBooks
      parameters:
        - { name: publisher, in: path, required: true, schema: { type: string } }
        - { name: pageSize, in: query, schema: { type: integer, format: int32 } }
        - { name: pageToken, in: query, schema: { type: string } }
      responses:
        "200":
          description: 一覧の1ページ分
          content:
            application/json:
              schema:
                type: object
                properties:
                  books: { type: array, items: { $ref: "#/components/schemas/Book" } }
                  nextPageToken: { type: string, description: 空なら最終ページ }
  /v1/publishers/{publisher}/books/{book}:
    patch:
      operationId: UpdateBook
      parameters:
        - { name: publisher, in: path, required: true, schema: { type: string } }
        - { name: book, in: path, required: true, schema: { type: string } }
        - name: updateMask
          in: query
          description: 更新するフィールドのカンマ区切り(例 displayName)
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/Book" }
      responses:
        "200":
          description: 更新後のリソースそのもの
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Book" }
components:
  schemas:
    Book:
      type: object
      properties:
        name: { type: string, readOnly: true }
        displayName: { type: string }

ポイントは3つです。UpdateはPATCHで、レスポンスは専用の型ではなく更新後のリソースを返します。一覧は pageSize/pageToken/nextPageToken の組で、トークンの中身は仕様書に書きません。出力専用のフィールドはOpenAPIの readOnly で表し、OUTPUT_ONLY 注釈の代わりにします。OpenAPIで仕様書を書くときの記載項目はAPI設計書の書き方|記載項目チェックリストとOpenAPIサンプル・レビュー観点にまとめています。

ErrorInfoのreasonとv1だけのパスをOpenAPIへ写すときの注意

エラーはAIP-193の形をそのままスキーマにできます。error.code(HTTPステータス)、error.status(NOT_FOUND などの正規コード名)、error.details[] の中の reason と domain を定義し、クライアントでは domain と reason の組でエラーを識別し、処理を分岐させます。message は人が読むための文面です。ただしAIP-193が変更を許すのは、そのRPCが当初から機械可読情報を持つ ErrorInfo を返してきた場合です。それ以外の既存RPCでは文面を維持し、説明の改善には LocalizedMessage を使います。

版はパスの先頭に /v1 だけを置き、OpenAPIの info.version とは分けて考えます。info.version は仕様書の改訂番号として細かく上げてもかまいませんが、パスの v1 は後方互換性を壊す変更のときだけ v2 に上げる運用です。RESTの原則そのものを確認したい場合はRESTとは?REST APIの仕組みと6原則・SOAP/GraphQLとの違いを実装目線で解説が出発点になります。

AIP準拠を自社APIへ全面採用するか部分採用で止めるかの判断基準

protoとgRPCで多数のAPIを揃えるチームが全面採用する条件

Google方式が向いているのは、APIをprotoで定義し、gRPCとREST/JSONの両方で公開するチームです。AIP-127は、リソース指向設計のRPCをREST/JSONの慣習に沿った形でも公開できる理由として「公開APIの80%以上がREST慣習の大部分に従っている」ことを挙げています。protoにHTTPマッピングを書いてRESTを生やす構成はgRPC-Gatewayとは?protoからRESTを生やす仕組みと生成構成・移行判断を解説で具体的に扱っています。

全面採用を勧められるのは、次の条件がそろう場合です。複数のチームがAPIを作り、命名やエラー形式がチームごとに揺れ始めている。クライアントライブラリをprotoから生成したい。api-linterをCIで回して、レビューの指摘を機械に寄せたい。いずれも、規約を文書で配るだけでは揃わなかった問題を、protoとリンターで強制できるようになります。

自社固有の規約を足すときは、番号の付け方に決まりがあります。AIPのサイトのAdopting AIPs in your companyは、AIP-9000〜9999を社外に公開しない「内部用」のブロックとして予約しており、Googleがこの範囲の公開AIPを今後作ることはないとしています。たとえば「リソースIDは acme_id で持つ」といった社内規則をAIP-9001として書けば、将来の公開AIPと番号がぶつかりません。

OpenAPI単独や少数APIのチームが部分採用で止めて見送る場面

逆に、OpenAPIでREST APIだけを設計しているチームが、AIPを丸ごと採用するのは勧めません。AIP-180自身が、この指針はprotocol buffersとJSONを前提にしていると断っていますし、api-linterが検査できるのもprotoファイルだけです。部分採用の出発点として、前章でOpenAPIへ写した4つの規約を優先します。どれもprotoが無くても設計判断として使えます。汎用的なAPI設計の手順全体はAPI設計とは?リソース定義からURI・スキーマ・エラー・版管理までの実装手順で整理しています。

呼び出し元が社内の1システムだけで、今後も外部公開の予定が無いAPIなら、カスタムメソッドのコロン記法やチャネル方式まで合わせる必要は薄いでしょう。既存のAPI群をどこまでAIPへ寄せるか、gRPCとRESTのどちらで公開するかの見極めから外部に相談したい場合は、一創のAPI開発・システム連携で設計レビューから実装まで対応しています。

Google API設計ガイドとAIP・api-linterに関するよくある質問

設計ガイドを調べ始めた人から出やすい質問を5つ選び、AIPとapi-linterの一次情報に沿って答えます。

Google API設計ガイドとAIPはどう違うのですか?

設計ガイドは2014年からGoogle内部で使われてきた規約集で、AIPは同じ指針を番号付きの文書として管理する仕組みです。ガイドの変更履歴によると、2024年10月に個別の設計ページがAIPへ転送され、2025年6月には命名規則のページも転送されました。いま規約の本文を読むならAIP(google.aip.dev)が正本です。ガイドの旧目次は、どのAIPを読めばよいかを引く索引として使えます。

Google API設計ガイドに日本語版はありますか?

docs.cloud.google.com の概要ページには日本語版があります。ただし個別の設計ページはAIPへ転送されており、転送先のAIPは英語で書かれています。本記事の対応表で論点からAIPの番号を引き、その番号のページだけを読むのが早道です。番号はapi-linterのルール名(core::0158 など)とも一致します。

Google API設計ガイドではページネーションをどう設計しますか?

AIP-158に従い、リクエストに page_size と page_token、レスポンスに next_page_token を置きます。トークンは利用者が解読できない不透明な文字列にし、next_page_token が省略されたら最終ページです。JSONではそれぞれ pageSize・pageToken・nextPageToken になります。絞り込みと並び替えを足すなら、filter と order_by の文字列を1本ずつ置きます。

Google API設計ガイドのバージョニングはセマンティックバージョニングですか?

メジャー版の考え方だけを借りています。AIP-185はマイナー版・パッチ版の番号を公開しないと定めており、URIやパッケージ名は v1 のようにメジャー版のみ、alpha・betaは v1beta のように付けます。互換性のある変更は同じ v1 の中で反映されるため、利用者は版を上げる作業なしに新機能を受け取れる仕組みです。互換性を壊す変更をするときだけ v2 を出します。

api-linterはOpenAPIのREST APIにも使えますか?

使えません。api-linterが検査するのはprotoファイルで、コマンドもprotoファイルを引数に取ります。OpenAPIで定義したAPIには、PATCH+updateMask、pageToken の不透明化、domain と reason の組による機械判定、パスは v1 だけ、の4点を設計レビューの確認項目として取り入れます。

関連記事

お気に入りに入れた記事の一覧

一創の公式 SNS

X では毎朝の新着記事と IT ニュースの速報を、YouTube・TikTok・Instagram・LinkedIn・Facebook では記事の解説動画を配信しています。

この記事は以下の記事からリンクされています

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.11.04 テックブログ CVE-2026-119977:WordPress更新中のRCEと安全な更新手順
  2. 2026.11.03 テックブログ クラブツーリズム不正アクセス:約432万件の対象確認と偽メールの見分け方・流出検知の設計
  3. 2026.10.29 テックブログ ソニー生命の情報漏洩38.5万件|ログインできない理由、再開状況と窓口の電話番号
  4. 2026.10.31 テックブログ 松屋の不正アクセス:237万件の対象確認と窓口、漏れたパスワードの守り方
  5. 2026.11.05 テックブログ Linuxカーネルの脆弱性CVE-2026-98432:修正版の見分け方とnftablesの緩和策

RELATED POSTS 関連記事

目次