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が正本です。本記事では、ガイドとAIPの関係、標準メソッド・命名・ページネーション・エラー・バージョニングの規約をAIPの原文に沿って整理し、最後にapi-linter 2.4.0で自作のprotoを検査した実行結果を載せます。

まとめ:Google API設計ガイドを使うときの要点

  • ガイドの個別ページは2024年10月から、命名規則のページも2025年6月からAIPへ転送されている。主要な参照先は google.aip.dev のAIP-121・122・131〜136・158・185・193・203などで、バッチ取得はAIP-231。
  • 設計の順番はAIP-121が示すとおり「リソース(名詞)→階層→スキーマ→メソッド(動詞)」。メソッドはGet・List・Create・Update・Deleteの標準メソッドに寄せ、合わないものだけを :archive のようなカスタムメソッドにする。
  • UpdateはPATCHが基本。PUTによる全置換は、フィールド追加が後方互換性を壊すためAIP-134が強く非推奨としている。
  • 一覧取得は page_sizepage_tokennext_page_token の3フィールドで組み、トークンは利用者が解読できない不透明な文字列にする(AIP-158)。
  • エラーは google.rpc.Status と正規のエラーコードで返し、ErrorInforeason は63文字以内のUPPER_SNAKE_CASEにする(AIP-193)。
  • 公開するのは v1 のようなメジャー版だけで、v1.1 は出さない。alpha・betaはチャネル方式で v1beta と付ける(AIP-185)。
  • protoでAPIを定義しているなら、api-linterをCIに入れる価値がある。ただしインストールは /v2/ 付きのパスで行い、CIでは --set-exit-status を必ず付ける。

Google API設計ガイドとAIPの関係

2014年から社内で使われてきた設計ガイドの位置づけ

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

見落とされやすいのが、ガイドの使用は必須ではないと明記されている点です。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年9月14日時点で73件が並んでいます。ガイドの目次がどのAIPに対応するかは次のとおりです。

論点 参照するAIP
リソース指向設計 AIP-121
リソース名 AIP-122
標準メソッド(Get/List/Create/Update/Delete) AIP-131135
カスタムメソッド AIP-136
フィールド名 AIP-140
長時間実行オペレーション AIP-151
ページネーション AIP-158
フィルタ AIP-160
後方互換性 AIP-180
バージョニング AIP-185
エラー AIP-193
フィールドの振る舞い注釈 AIP-203

リソース指向設計と標準メソッドのHTTP対応

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

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

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

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

綴りはAIP-190が「licenceではなくlicense、colourではなくcolor」とアメリカ英語を指定しています。

標準メソッド5種とHTTPメソッドの対応

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

メソッド HTTP URIの例 AIP
Get GET /v1/{name=publishers/*/books/*} 131
List GET /v1/{parent=publishers/*}/books 132
Create POST /v1/{parent=publishers/*}/books 133
Update PATCH /v1/{book.name=publishers/*/books/*} 134
Delete DELETE /v1/{name=publishers/*/books/*} 135

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

フィールド命名とページネーション・フィルタの規約

protoのlower_snake_caseとfield_behavior注釈

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

もう一つ、Google方式に特有なのがAIP-203の google.api.field_behavior 注釈です。リクエストで使うメッセージのフィールドには原則として付けます。ただし、AIP-154のリソースのetagフィールドは付けないことが推奨され、oneofフィールドは省略が許されています。以下はこれらの例外を除く規則で、最低でも REQUIREDOPTIONALOUTPUT_ONLY のいずれかを使います。注釈はproto自体の動作には影響しません。それでもCLIやSDKがこの注釈を頼りにコードを生成するため、付け忘れはクライアントの品質に直結します。注釈が無いフィールドは後方互換のために OPTIONAL と解釈されますが、注釈の省略そのものは禁止されています。

page_tokenによるページネーション(AIP-158)

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

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

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

絞り込みはAIP-160が扱います。APIごとに専用の構造体を作るのではなく、構造化された構文を持つ文字列フィールド1本(filter)で受ける方針です。フィルタの要件は頻繁に変わるから、というのが理由です。

エラーとバージョニングの規約

google.rpc.StatusとErrorInfo(AIP-193)

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は、すべてのエラーレスポンスの detailsErrorInfo を含めることも必須にしています。機械判定に使うのはその reason です。エラーの領域(domain)の中で一意な値で、63文字以内、正規表現 [A-Z][A-Z0-9_]+[A-Z0-9] に合うUPPER_SNAKE_CASEと決められています。AIPが挙げる良い例は CPU_AVAILABILITYNO_STOCK などです。ErrorInfo が無いとクライアントは Status.message の文面を解析するしかなくなり、その文面が暗黙の契約になって変更できなくなる、というのがAIP-193の説明です。16種のコードの意味とHTTPステータスの対応はgRPCのステータスコードとは?16種の意味・HTTP対応表・リトライ可否を解説にまとめています。

メジャー版だけを出すv1方式とチャネル(AIP-185)

AIP-185は、すべてのGoogle APIインターフェースにメジャー版番号を持たせ、protobufのパッケージ名の末尾とREST APIのURIパスの先頭に入れるよう定めています。セマンティックバージョニングと違い、マイナー版・パッチ版の番号は公開しません。v1.0v1.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)

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

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

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

api-linterでprotoのAIP準拠を機械チェックする手順

v2系のインストールとimportの用意

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

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

/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 でパスを通します。

違反を含むprotoと検出結果

次の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-snake0158 の3ルールと 0132::request-unknown-fields0134::request-mask-required0134::http-method として検出されています。加えて、method_signaturefield_behavior の注釈漏れも拾われました。表の行の並び順は実行のたびに変わったため、差分比較には --output-format json を使うほうが安全です。

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

修正後のprotoで違反0件を確認

検出結果に沿って直したファイル全体です。Updateは patchupdate_mask、Listは page_sizepage_tokennext_page_token、リソースには singularpluralIDENTIFIER 注釈を付けています。

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の指針すべてをルールで表現できるわけではなく、リンターは指針を読んで理解することの代わりにはならない、と注意しています。リソースの切り方やカスタムメソッドにすべきかどうかの判断は、ツールでは検出できません。

AIP準拠を自社APIへ持ち込む判断基準

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

逆に、OpenAPIでREST APIだけを設計しているチームが、AIPを丸ごと採用するのは勧めません。AIP-180自身が、この指針はprotocol buffersとJSONを前提にしていると断っていますし、api-linterが検査できるのもprotoファイルだけです。この場合に取り入れる価値が高いのは、PUTではなくPATCH+更新マスク、不透明なページトークン、機械判定用の reasonv1 だけを公開する版管理の4つです。どれもprotoが無くても設計判断として使えます。汎用的なAPI設計の手順全体はAPI設計とは?リソース定義からURI・スキーマ・エラー・版管理までの実装手順で整理しています。

よくある質問

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

設計ガイドは2014年からGoogle内部で使われてきた規約集で、AIPは同じ指針を番号付きの文書として管理する仕組みです。2024年10月以降、ガイドの個別ページはAIPへ転送されているため、現在の規約本文はAIP(google.aip.dev)で読みます。

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

docs.cloud.google.com の概要ページには日本語版があります。ただし個別の設計ページはAIPへ転送されており、転送先のAIPは英語で書かれています。

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

AIP-158に従い、リクエストに page_sizepage_token、レスポンスに next_page_token を置きます。トークンは利用者が解読できない不透明な文字列にし、next_page_token が省略されたら最終ページです。

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

メジャー版の考え方だけを借りています。AIP-185はマイナー版・パッチ版の番号を公開しないと定めており、URIやパッケージ名は v1 のようにメジャー版のみ、alpha・betaは v1beta のように付けます。

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

使えません。api-linterが検査するのはprotoファイルで、コマンドもprotoファイルを引数に取ります。OpenAPIで定義したAPIには、AIPの考え方を設計レビューの観点として取り入れる形になります。

関連記事

資料請求

RELATED POSTS 関連記事