APIの変更

バージョン番号のないGraphQLの非推奨化

1分で読めます

REST APIは/v1/の隣に/v2/を公開し、呼び出し側に自分のペースで移行させることができる。GraphQLは一つのエンドポイントに一つのスキーマを持ち、去年のビルドのモバイルアプリと今朝デプロイされた内部ダッシュボードを含む、すべてのクライアントが同じグラフに問い合わせる。フォークするURLは存在しない。フィールドを非推奨化するとは、すでに全員が依存しているスキーマの中で、その場で非推奨としてマークすることを意味し、これによって規律はRESTとは異なるものになる。呼び出し側に何かがなくなると伝えるという根底の問題は、API非推奨化が一般的に扱う問題と同じであるにもかかわらずだ。

上げるべきバージョンがない場合、GraphQLはどうやってフィールドを非推奨としてマークするのか

フィールドに直接適用される@deprecatedディレクティブによってだ。

type Product {
  price: Float @deprecated(reason: "Use priceV2 for multi-currency support.")
  priceV2: Money
}

フィールドは問い合わせ可能なままだ。消えることも、404を返すことも、動作を変えることもない。ただ、GraphiQL、Apollo Studio、スキーマリンターなど、ほとんどのGraphQLツールが、スキーマを閲覧したりそれに対してクエリを書いたりする誰にでも表示する、機械可読な注記を運ぶだけだ。これがメカニズムのすべてだ。別個の非推奨化エンドポイントも、ヘッダーも、仕様が要求する付随文書も存在しない。これがこのディレクティブの魅力であり、同時に罠でもある。ディレクティブを追加するのは簡単で、無視するのも簡単だ。クライアントにそれを見るよう強制するものが何もないからだ。

非推奨化の理由を実際に見る人はいるのか

スキーマを直接使う人、つまりイントロスペクションやスキーマを意識したエディタを通じて使う人だけであり、それはAPIチェンジログの通常の読者よりも小さな読者層だ。六か月前のクエリに対して構築されたモバイルアプリは、そのクエリをすでにバイナリに焼き込んでいる。誰かが新しいフィールドでアプリを再構築し、アップデートを公開するまで、非推奨であろうとなかろうと、priceを要求し続け、答えを受け取り続ける。ディレクティブは新しいコードを書く開発者に、古いフィールドを使わないように伝える。すでに公開され稼働しているクライアントには何もしない。

メカニズム誰に届くか
@deprecatedディレクティブスキーマを閲覧したり新しいクエリを書いたりする開発者
スキーマリンターのCI失敗実行していれば、クライアントのコードベースを所有するチーム
チェンジログのエントリリンターを持たないクライアントチームを含む、それを読む誰でも
何もなし(フィールドはただ動作する)古いフィールドを使う、すでに構築済みのクライアント

非推奨化されたフィールドはそれでもチェンジログのエントリを持つべきか

はい、そしてそれはディレクティブ単独より多くの仕事をする。なぜならチェンジログはディレクティブが届かない人々に届くからだ。スキーマを閲覧せずにグラフを消費するパートナーチーム、数か月前にキャッシュされたスキーマのコピーに対して構築されたクライアント、散文を読むことでしか気づかないであろう誰か。APIチェンジログは、エントリが呼び出し側に一般的に何を負っているかを扱う。GraphQLのエントリは、RESTがめったに明示する必要のない一つのことを負っている。なぜならRESTの呼び出し側はそれをバージョン番号から推測できるからだ。古いフィールドが今日もまだ機能しているのか、警告付きでまだ機能しているのか、あるいは実際にデータを返さなくなったのか。ディレクティブ単独では、スキーマを一度も開いたことのない読者にとって、これらのどれにも答えない。

フィールドをスキーマから削除するのが実際に安全なのはいつか

クエリログが誰もそれをもう求めていないことを示す時だけだ。これは使用状況の問題であり、カレンダーの問題ではない。フィールドは一年間@deprecatedを持ちながら、一度も再構築されていない一つのクライアントにとって依然として重要でありうる。RESTのSunsetヘッダーがよくやるように、固定されたスケジュールでそれを削除することは、対応できる警告なしにそのクライアントを壊す。なぜならGraphQLは、一度も読んだことのないディレクティブ以外に、対応できるものを何も与えないからだ。削除日にコミットする前にフィールドレベルの使用状況をログに記録し、ゼロでないクエリ数はカウントダウンではなく、保留として扱うべきだ。

フィールドを追加することはREST APIと同じリスクを伴うのか

構造的には、新しいフィールドについては少ない。なぜならGraphQLクライアントは明示的に要求したフィールドしか受け取らないからだ。priceの隣にpriceV2を追加しても、RESTのJSONレスポンスにフィールドを追加することが厳格なデシリアライザーを壊しうるのと同じ方法で、既存のクエリを壊すことはできない。クライアントに新しいフィールドを要求するよう強制するものが何もないからだ。既存のenumに新しい値を追加することは、同じ文脈で名指す価値のある例外だ。強く型付けされた言語が推奨するように、すべてのenum値を網羅的にswitchするクライアントは、どのクエリがそれを求めたかにかかわらず、新しい値が届いた瞬間に壊れる。この安全性は、クライアントが明示的に選び取るフィールドとユニオンメンバーにしか成り立たず、クライアントのコードが手作業で列挙する閉じた集合には成り立たない。

GraphQLのチェンジログエントリがREST のエントリには必要ないものは何か

フィールド名だけでなく、クエリの形だ。なぜなら「priceフィールドは非推奨」は、呼び出し側が本当に必要とする部分、つまりどの型とどのクエリがそれに触れているかを欠いているからだ。有用なエントリは、型、フィールド、代替フィールドを名指しし、生成できるなら、まだ古い形を要求している本番環境の実際のクエリを名指しする。その最後の部分、非推奨化の通知を実際の使用状況に結びつけることは、RESTの呼び出し側がURLに対するサーバーログから無料で得られるもので、GraphQLの呼び出し側は得られない。なぜなら、何を要求していようと、すべてのクエリが同じエンドポイントに当たるからだ。

フィールド以外のものが@deprecatedディレクティブを持てることはあるか

enum値だ。フィールドの定義ではなく、その値自体の定義に同じディレクティブを使う。

enum ShippingMethod {
  STANDARD
  EXPRESS
  OVERNIGHT @deprecated(reason: "Use EXPRESS with priority: true instead.")
}

仕様が@deprecatedを定義しているのは正確に二つの場所、フィールド定義かenum値だけであり、安定版リリースの時点ではそれ以外にはない。引数レベルや入力フィールドレベルの非推奨化は、より後のドラフト言語にのみ存在し、今日ほとんどのサーバーが実装しているものには含まれない。このようにマークされたenum値は、サーバーがそれでもなお返したり受け付けたりできる正当な値であり続け、非推奨のフィールドが持つのと同じ「壊れない」約束を果たす。だからこそ、実際にその値を削除する前に安全に出荷できる。

FAQ

GraphQLはエンドポイント全体に対するSunsetヘッダーのようなものをサポートしているか? いいえ、通常エンドポイントは一つしかないからだ。非推奨化のタイミングはフィールドレベルで、@deprecatedディレクティブの理由テキストと、チームがその隣に公開するチェンジログや移行ガイドの中に存在する。クライアントがプログラム的に読める応答ヘッダーの中にはない。

非推奨化されたフィールドは削除された後、異なる型で再追加できるか? 新しいフィールド名としてのみだ。型を変えて同じフィールド名を再導入することは、非推奨化サイクルがまさに避けるために存在する破壊的変更そのものだ。priceV2がそうしているように、代替には独自の名前を与え、名前が再利用可能になる前に、古いものを完全に消滅させよ。

@deprecatedの理由テキストはチェンジログのエントリにリンクすべきか? スキーマツールがそれをサポートしているなら、はい。理由フィールドは単純な文字列を受け入れ、その文字列内のURLは、イントロスペクションの出力を見つめる開発者から、チェンジログのエントリが与えられるより完全な説明への最短経路だ。

GraphQLのスキーマ変更が、RESTがそうでない方法で後方互換になることはあるか? 加算的なフィールドの変更は、上記の理由からある。クライアントは要求したものしか受け取らないからだ。新しいenum値は例外であり、閉じた集合を列挙するクライアントは、想定していなかった値によって壊れることがある。削除と型変更は、RESTの同等物とまったく同じように破壊的だ。


この記事の技術的な記述は第三者による確認を受けていません。誤りがあればお知らせください。修正します。

changeloopの関連ページ: 開発者向けドキュメント, changelogツール比較

changeloop
ループを閉じるchangelogを作っているチームです。ユーザーが何かを求め、あなたのチームがそれを届け、求めた人がそれを知る。