RESTful API の後方互換性

問題

次の条件を考慮した時、API の変更をどのように行うべきでしょうか。今回は、特に API の変更前後で後方互換性が失われる場合にどのように対処すべきか、という点のみを焦点とします。

  • API は REST 的であることとする
  • サーバの実装は、API の変更の有無によらず、必要に応じて随時変更される
  • API は、可能な限り安定しているべきであるが、必要に応じて変更されることがある
  • API の変更は、後方互換性を保てる場合と、保てない場合がある
  • いかなる変更がされたとしても、既に存在しているクライアントは、少なくとも一定期間は問題なく動作すべきである

経緯

ナビゲーショナルに次の要求を決定できるということは。REST 的な重要な特徴であるのは疑う余地はありません。この特徴は念頭においた上での今回の問題提起でした。

#rws 後方互換性を破壊するようは変更が出て来ることを考えると。API の非互換バージョンごとに URI セットを振り分けられるようになっているのが望ましい。では、URI に互換バージョンを表す識別子を含めるか。それはそれで気持ちよくない。

http://wassr.jp/user/siena/statuses/A1STwc4qWx

これに対して、id:IwamotoTakashi (id:iwamot) さんから次のような記事をいただきました。

API後方互換性」問題は、そもそも気にならないということになります。互換性が気になるのは、クライアントとサーバが密結合しているサインだといえるでしょう。

http://d.hatena.ne.jp/IwamotoTakashi/20090501/p1

ある特定の時点での API のスナップショットについては。id:IwamotoTakashi さんの記事の最終段落より前に書かれている通りです (これに関連した、あるいは、派生した論点はあるかもしれませんが、措いておきます)。

しかし。ここでの急所は、「後方互換性を破壊するような変更」であり。着目点は、変更前後の API のスナップショット間の関係です。その前段までの説明と、この問題には、直接の関係はありません。

というわけで。考察の過程の説明に、もう少し言葉を費やしてみることにしましょう。どなたかが、論理の穴をついてくれることを期待して。

前提の確認

まず、クライアントから除くことのできない処理要件は。「応答として得られたリソース表現である文書を適切に処理する」といったことでしょう。ここで考える「処理」は、次が主要なものだと考えます。

  • 1. 文章中から部分データを抽出し、必要なら別の構造を持つデータに再構成する
  • 2. 適切なワークフロー (もしくはデータフロー) となるようなリンクを選択し、必要ならばパラメータを設定して、要求を送る
  • ※ リンクはフォームとして与えられるものも含めます

ある情報を得るために利用される URI は、次のように取得されるとしましょう。

URI 取得方法 A.
あるクライアントが、API 変更前に既にリンク先の URI を取得していて、API 変更後にそれを用いて要求を発行しようとすることは十分にありえます。リソースがアドレス可能なのですから、これは自然なことです。
URI 取得方法 A'. (以下、A. に含める)
また、URI が分かっているのであれば、毎回ナビゲーショナルに URI を取得していくというのも面倒な話です。C/S を URI で結合するか、リンクナビゲーションパスで結合するか。どちらにしても結合性が生まれるのですし、アドレス可能性という点からも URI での直接アクセスが良くないとは言えません。場合によっては、ナビゲーショナルな URI 取得の方が密な結合になってしまうこともあります。
URI 取得方法 B.
このリソースの URI は、別のあるリソース中に、メディアタイプと関係名から決定されるリンクとして含まれています。ですから、ナビゲーショナルに必要な URI を取得することができます。この URI は従属的に決定されるので、既存のものとは異なっていても全く問題はなく、クライアントは正常に処理を継続できるはずです。(少なくとも次のリソースにアクセスしようとするところまでは)

API 変更の影響と後方互換性の維持

さて、API が変更された時。
一般に、クライアントが同時に追従することはできません。たとえ事前に変更が周知されていたとしても、開発者の都合ですぐにはメンテナンスされないかもしれません。

ですから。クライアントの動作を破壊しないために。前述の A. 既知の URI、もしくは B. メディアタイプと関係名から決定された URI、に対する要求は。いずれも、API の変更前に対して、変更後も後方互換性が保たれている、即ち。既知の要求を全て受理可能であり、かつ、既存の API で期待される結果と競合しない副作用のみを発生させ、かつ、上位互換な結果文書を返す必要があります。そうでないと、クライアントは困ってしまいかねません。例えば、適切なワークフローとなるようなリンクを選んだはずなのに、予期しない副作用によって処理が破綻したり。応答文書が予期しない構造になっていて、想定していた処理ができなくなったり。

では、後方互換性を失う変更をする場合にどうすれば良いでしょうか。まず、A. に対して後方互換性を保つためには、従来の URI を持つリソース X はそのままにして。異なる URI を持つリソース X' を新たに定義し、他から参照されるようにリンク関係を変更するのが簡明でしょう。

ただし、そのリンク元となっているあるリソース Y と新しいリソース X' はリンクされ、一組のものとして扱われることになります。このため、全体としては、まだ B. に対して後方互換性を保てていません。なぜなら、API 変更前のリソース Y に到達した古いクライアントは、リンクを辿って X' に到達してしまうからです。

というわけで、後方互換性を維持するためには。Y に対して X' を参照するような変更をするのではなく。X' へのリンクを持つ Y' というリソースとして新たに定義することになります。こうやって、更に影響範囲を遡って括り出していくと。結局、API 変更前/変更後のリソース群が独立して存在することになるわけで。即ち、それぞれが異なるバージョンの API を表現していることになります。

動作が違えば別の API

さて。これらの異なるバージョンの API となるリソース群を同じリソース空間に混在させると要らぬ混乱を生みかねません。とすれば、なんらかの形で、明示的に異なるものであると分かるようにするのが賢明であるように思えます。という経緯で、冒頭の結論に至ったというわけです。

が、やっぱりなんだかなぁ、な気もしていたりして ^^; きれいな解決方法がないものかしらん。

改訂履歴

  • 2009/05/04 Mon 14:40 JST
    • 冒頭に問題を追記
    • 一部の表現を微修正
    • iwamot さんの ID を修正 (iwamot→IwamotoTakashi)
  • 2009/05/04 Mon 22:50 JST
    • API 変更の影響と後方互換性の維持」での後方互換性についての要件を若干修正
      • 旧: 上位互換な副作用のみを発生させ、かつ、上位互換な結果文書を返す
      • 新: 既知の要求を全て受理可能であり、かつ、既存の API で期待される結果と競合しない副作用のみを発生させ、かつ、上位互換な結果文書を返す