Slug kyle fuller cover

強くしなやかなAPIクライアントを作る

現在使われているAPIのバージョニングの手法では、仕様変更にまつわる問題を実際に解決していません。ただ問題を先送りにしているだけです。Kyle Fullerは、APIとAPIクライアントを別々に開発するための手段としてのRESTアーキテクチャーについてお話します。彼は、RESTに関する一般的な誤解を解き、そのような強くしなやかなAPIクライアントをどのように設計するのかについて、デモンストレーションを行います。


強くしなやかなAPIクライアントの設計 (0:00)

今日は、APIの実装の詳細への理解を必要としない、APIの変更に適応することができる強くしなやかなAPIクライアントの設計についてお話いたします。クライアントがどのようにして設計されたか、そしてよく起こる問題についてまずお話しましょう。

私はPollsという、シンプルなiOSのデモアプリケーションを制作しました。複数の選択肢がある質問の一覧が表示され、最も当てはまる回答に投票するというものです。質問はスワイプして削除することができ、タップすると詳細を見ることができます。詳細画面で回答を投票することもできます。最初の画面か、質問の一覧から、作成ボタンをクリックすれば自分の質問を作成することができます。

どのように動くのか? (1:37)

さて、このアプリケーションに何ができるのかがわかったところで、このアプリの実装についてと、どのようにAPIとやりとりしているかについて話をしましょう。

最初の質問の一覧のAPIは、HTTPのGETメソッドで取得されるでしょう。このAPIはJSON形式で質問を返すとします。

質問をスワイプして削除する時には、その質問を削除するためのURIが必要です。ここでは、idからURIを作ることにし、ユーザーは質問を削除することを許可されていて、削除を実行できるとします。

質問自体へ移動すると、質問をダウンロードするために、クエリパラメータのidを使用して、別のURIを構築する必要があります。

IDはサーバーサイドの実装の詳細であることを覚えておいてください。この場合、オートインクリメントされたIDを持つリレーショナルデータベースから導出されます。

このように、クライアントがIDを知っていて、かつどのように動作するかを理解して使用してるのは、不条理に見えます。これは、APIの中でどのようにIDがサーバに格納されているのかについての実装の詳細です。もしユーザーが選択肢をタップして投票を行おうとした時には、2つのIDから、URIを導出します。

そのあと、いくつかのビジネスロジックをサーバサイドに重複して実装する必要があるでしょう。もしすでに質問に投票してた場合、もう一度投票することはできるでしょうか?私たちはクライアント自身にもそのようなルールの実装を持つ必要があります。

最初の画面に戻って「作成」を選ぶとフォームが表示されます。そこでAPIがどのような入力を受け入れるのか、入力はどのようにエンコードする必要があるのか決めなければなりません。ここではAPIはリクエストボディでエンコードされた入力を受け付けることにします。つまり暗黙の仕様から私たちのアプリケーションが構築されていることは明らかです。

さらに、APIがどのように動作するかの理解があるので、ある決まった方法でアプリケーションを設計しました。この暗黙の仕様を中心にしてアプリケーションを設計することで、アプリケーションとAPIの振る舞いは、本当に密結合になります。このことは、最初は大した問題には見えないでしょうが、いずれは変更しなければならない問題となるでしょう。

APIバージョニング (4:03)

みなさんはよくまったく異なるバージョンのAPIを作成することで、APIバージョニングに関しての問題を解決しますね。

APIに関わる部分の開発を速く進めようとすると、しばしば互換性を無視して、破壊的にAPIの振る舞いを変更します、しかしながら、その変更によって古いクライアントが破損されるべきではありません。私たちは何も壊すことなく、素早く開発を進めることを望んでいます。

APIの互換性を失う変更を行う場合、私たちはたいてい事前に申請を読んで、ソースコードをアップデートする必要があります。

iOSではたいてい1〜3週間のレビュー期間が存在するため、私たちはすぐに変更することができません。もし私たちがクライアント側のアップデートを提供しない場合、壊れたアプリケーションだけが残ることになります。それは簡単に、すぐに変更を再デプロイできるはずのWebの性質とは異なります。

記事の更新情報を受け取る

いくつかのAPIプロバイダは、彼らのAPIの各バージョンを非常に長い期間保持すると言っています。例えば、Facebookは彼らのAPIを二年間の間、保持すると言っています。それば素晴らしいことですが、結局は新しいAPIにいつかは切り替える必要があります。

APIのバージョンを凍結することは実際にはその問題を解決しません。それはただ問題を先送りにしているだけです。そしてそれをすることは、APIチームにレガシーなAPIをメンテナンスすることを要求します。そのため、頻繁にAPIに新しい変更を導入することは難しくなります。

何も破損することなく、素早く開発を進める (5:39)

これから先に起こる変更に適応した、より強くしなやかなクライアントを開発することで、速く開発を進めることが出来て、何も破損することがなかったら、それは素晴らしいことではありませんか?

私はみなさんはRESTのことをよくご存知であると思っています。

RESTはAPIを構築するための、変化を受け入れることができるようによくデザインされた、一般的なアーキテクチャです。RESTは制約の集まりで作られ、サーバとクライアントが独立して変更することができ、ウェブはこれによって形成されています。RESTの制約の多くが変更を受け入れたり予測するために特別にデザインされています。もしWebサイトが再デプロイされる度に、クライアントがアップデートされる必要があったとしたら、それはばかげたことでしょう。

人々は、しばしばRESTをCRUDのように、優れたURL、JSON、HTTPメソッド、およびルーティングのようなものだと思っていますが、それは実際のRESTの正体ではありません。むしろリソースと、リソースの表現についてです。アプリケーションの状態を表すマシーンとしてRESTを利用します。

クライアントがAPIの振る舞いをハードコーディングしてしまっている場合、APIを簡単に変更することはできません。APIについての暗黙の前提を組み込んでAPIクライアントを作成することは、結果的に密結合を生じさせます。

Representational State Transfer、つまりRESTを使用することによって、それを成し遂げることができます。サーバからクライアントへ、状態の表現を転送することができます。アプリケーションで質問を表示する場合、サーバ側から、現在の状態から別の状態にどのように遷移できるかを示すべきです。

HATEOAS (7:27)

これは、RESTの背後にある主要な制約の1つであるHypermedia as the Engine of Application State、またはHATEOASと呼ばれるものによって実現されます。

質問のリソースについて考えると、これから行われることは2つのこと、第一に新しい質問を作ること、第二にそれを一覧表示すること、になるでしょう。

それぞれの各質問のリソースを見てみると、これもまた2つのうち1つのこと、そのリソースを削除するか、その質問と選択肢を表示させるか、を提供することになります。

特定のケースでは各選択肢のリソースは1つのこと、私たちがそれに投票することを可能にします。

ドメインの意図をクライアントに教えることで、より良いAPIクライアントを設計することができます。この場合のPollsの背景にある意図は、名前です。

暗黙の前提を利用してクライアント作るのではなく、実行時にこれらの扱いについて知るべきです。実装の詳細を分岐したり、前提の上にアプリケーションをビルドすることなくやる必要があります。

代わりに、サーバからアプリケーションをロードし、おおよそのAPIのふるまいを知ることができます。APIに、それがどのような状態遷移を提供するか、また、何の状態が可能なのかを尋ねることができます。ではAPIによって質問の一覧を表示するには、具体的にどうしたらいいでしょうか?

それぞれの質問について、APIが異なる状態にト遷移させるかどうかを判断する必要があります。APIが投票するための状態遷移を提供するかどうかを確認すべきです。ユーザーがすべての選択肢に投票できるかどうかは、サーバのロジックを複製してクライアントで暗黙的に判断すべきではありません。

APIを使って、私たちが質問を作成できるのか、またどのようなパラメーターがそれらの質問を作成するのに使用されるのかを確かめるべきです。

このような情報をハードコーディングすることは、クライアントが変更されること、クライアントがアップデートされずにサーバが変更されることを不可能にします。

HALとSiren (9:51)

どのようにして、サーバからクライアントへ実行時の情報を取得するのでしょうか?

残念ながら、多くのAPIは、APIがどのように動作するのかに関する情報を実行時に提供していません。APIがどのように動作するのかや、それに関する情報を、公開する方法の1つが、ハイパーメディアコンテンツタイプを使用することです。HALSirenと呼ばれる2つの一般的な方法があります。

それらはどちらも標準化されたコンテンツタイプであり、またどちらもJSONの上で設計されたものです。あなたはHALかSirenのどちらかのサポートするようにAPIを実装する必要があります。

HAL (10:24)

HALはHypertext Application Languageという意味です。それはあなたのAPIの中のリソースをリンクするための、簡単で一貫性のあるシンプルなフォーマットです。HALを採用することは、あなたのAPIの見通しを良くし、自身の記述をAPI自身から発見しやすくします。あなたのAPIを使いやすくし、おそらくクライアントデベロッパー達にとってより魅力的にするでしょう。

HALは2つの新しいパラメータを追加して、データ構造を拡張します。リンクの集まりである_linksと、リンク先のリソースの代わりになる埋め込みリソース、を追加します。埋め込みリソースはリンクをたどる代わりに、APIの振る舞いを変更できることを意味します。APIクライアントにそれを埋め込むことができ、クライアントはAPIが変更されたことを理解するでしょう。

HALは現在、最も人気のあるハイパーメディア規格の1つであり、新しいGitHub APIでも使用されています。別のリソースへのシンプルなリンクはこのようになります。

{
    "links": {
        "question": { "href": "/questions/1" }
    }
}

リソースとhrefリンクのリレーションを持っています。リンクをたどる代わりになる、別のリソースを埋め込むことができ、サーバはクライアントからの利用を最適化するため使用することができます。

クライアントデベロッパーは、リンクをたどる前にまず最初に埋め込まれたものを確認する必要があります。

{
    "_embedded": {
        "choices": [
            {
                "_links": {
                    "self": { "self": "/questions/1/choices/1" }
                },
                "choice": "Swift",
                "votes": 2048
            }
        ]
    }
}

Siren (11:54)

Sirenは、もう1つのハイパーメディアの仕様ですが、エンティティを表します。HTMLがウェブサイト上でドキュメントを視覚的に表すために使用されるように、SirenもウェブAPIを介してエンティティを提示するための仕様です。

Sirenは、エンティティについての情報をやりとりする機能、それらのアクション、状態遷移の実行、そしてナビゲートするためのクライアントのためのリンクの構造を提供しています。

Sirenを使用すると、名前、URI、そしてHTTPメソッドと一緒にあなたのAPIのアクションを定義することができます。また、状態遷移を作成するために必要な属性かフィールドを表現することができ、また、提案されたコンテンツタイプをクライアントに送信することもできます。

どちらのコンテンツタイプでもあなたはAPIを変更してそれらを実装する必要があります。次に示すフレームワークを利用してそれらを自動的に実装することもできます。このフレームワークはAPIの仕様であるAPI Blueprintと呼ばれる言語をサポートしています。

これであなたはプレーンなAPIとやりとりすることができます。またAPIがどう動作するかの仕様をサイドローディングして、同様のインターフェースを果たすためにそれらを利用します。API Blueprintは、現実的な実装を設計する前に、APIが通信することを可能にする仕様言語です。すでにAPIの仕様を記述しているので、API BlueprintはAPI仕様を変更できるように実際のクライアントを動かします。クライアントはマシンリーダブル(機械可読)なので、それらの変更を利用することができます。

Hyperdrive (13:35)

Hyperdriveで、私たちはそのルートURIによって、APIを入力することができます。この場合、私たち���HTTP URLにアクセスして、実行時にAPIがどのように動作するかを決定することができます。これにより、アプリケーションの起動する合間にAPIがどのように動作するかを変更することもできます。クロージャで渡したAPIを入力する時に、そしてこれは同期的に実行されようとします。APIが成功した時、私たちは表現を渡されることになるでしょう。

hyperdrive.enter("https://polls.apiblueprint.org/") { result

}

representorはAPIリソースのための標準的なインターフェースです。この場合、私たちのAPIのルートを表します。representorはリソースとサービスの表現であり、種々の異なる属性が含まれています。最も重要なものの1つは、おそらくあなたが最も精通しているものでしょう。そう、それはリソース自体の属性です。また、異なるリソースを使用してこのリソースから作成することができるトランジションも与えられます。

私たちは、実行時に新しい機能について学ぶために、現在のAPIリソースから別のAPIリソースへの状態遷移を内省することができます。私たちはまた、埋め込まれる可能性のある他の関連リソースの集合である、representorsと呼ばれるプロパティを与えられます。トランジションが起こる前に、クライアントはここを確認するべきです。これでサーバに再び最適化をさせることができます。

ルートリソースが返ってきた時点で、私たちはその上に何があるかを内省できます。この例では、私たちが質問リストとして認識している"questions"へのトランジションがあるかを確認することができます。もしそうなれば、私たちはそれを実行し、このトランジションに続くことができます。これは、ハイパーリンクをフォローするハイパードライブをもたらし、結果として生じたリソースのための新しいrepresentorが与えられます。

if let questions = representor.transitions["questions"] {

}

hyperdrive.request(questions) { result in

}

この場合、質問リソースの集合を含んだリソースを取り戻します。私たちはそれらそれぞれをループし、そして mapを使用して関数 viewQuestionを呼び出します。私たちのAPIは変更することができるので、これらの質問が既に存在することを期待すべきではありません。私たちはその機能が、後に欠落するであろうということ、うまくそのインターフェースに適応する、という事実を処理する必要があります。

if let questions = representor.representors["questions"] {
    map(questions, viewQuestion)
}

元々、viewQuestionは質問の属性を探し、そしてそれを送るために出力するだけです。そして次に、選択肢などの他のリソースへのリレーションを探します。もしそうなれば、それらの上にマップするものはそれぞれのchoiceをループさせ、viewChoiceメソッドを呼び出します。そして最終的に、各リソースがスワイプして削除することができるかを示す、デリーターのための状態遷移があるかを確認します。

func viewQuestion(question:Representor<HTTPTransition>) {
    println(question.attributes["question"])
    if let choices = question.representors["choices"] {
        map(choices, viewChoice)
    } else {
        println("-> This question does not have any choices.")
    }

    if let delete = question.transitions["delete"] {
        // User may delete this question
    }
}

私たちのviewChoiceメソッドは質問の各選択肢のために呼び出され、選択肢と投票された票の数を出力し、そして投票するための状態遷移があるかを確認します。それぞれの異なったリソース間で変動する可能性があるため、個々にこれを確認するべきです。二度投票することができるか否か、またこのようなビジネスロジックについて心配する必要はありません。なぜならサーバがそれを処理するからです。

func viewChoice(choice:Representor<HTTPTransition>) {
    let text = choice.attributes["choice"]
    let votes = choice.attributes["votes"]
    println('-> \(text) (\(votes))')

    if let vote = choice.transitions["vote"] {
        // User may vote on this choice
    }
}

これはクライアントに変更を行わせます。APIも変更できます。API上のロジックを変更できるので、クライアントは何も変更する必要がありません。投票を可能にするためのト状態遷移がある時、どのようにAPIが動作しているのかを理解する必要も、そのURIがどのように設計されているのか、どのHTMLメソッドを使用するか、どのようにデータをエンコードするのか、さらにはHTTPさえも知る必要はなく、それを進められます。

質問の一覧を表示させる場合、クライアント側で新しい質問を作成するための状態遷移を許可するかも決定することができます。もちろん、新しい質問を作成するための機能は消去されるかもしれません。クライアントではそのようなケースなのか特定して、インターフェースからきれいに削除するべきでしょう。

if let create = questions.transitions["create"] {
    // We may create a new question

    for attribute in create.attributes {
        // Creation takes `attribute.name`
    }
} else {
    // Gracefully handle the lack of being able to create a question
}

なぜあえてこの方法で設計するのか? (18:00)

なぜわざわざAPIクライアントとサーバをそのような方法で設計するのでしょうか?

利点の1つは、アプリケーション周りのロジックを変更することができることです。機能の動作を完全に変更することができますし、そしてクライアントはシンプルに適応できます。APIの機能を完全にも一時的にも消去することができます。機能の折衝を無料で手に入れられます。そして異なった状態遷移を異なったユーザーに公開できます。A/Bテストを表示できますし、どのようにAPIが動作するのかを変更することもできます。

アドミンはただ質問を削除することしかできませんが、それだけができればいいとしましょう。私たちのAPIはシンプルにただ、アドミンにこの状態遷移を渡すことしかしません。クライアントやコードの変更は一切なく、クライアントは単にこれに適応します。私たちはアプリが開発された時に存在していなかった新しい状態遷移を追加することができます。

またそれは、現れたボタンの中に表示されることができ、それを表示させるためにサーバからの情報を使用します。クライアントは特定のキーワードの意図で設計されることができます。クライアントは、新しいリソースを作成する必要があるかを仮定するcreateという用語を理解するし、または、deleteという用語を理解する、また、そのアフォーダンスをもっている時、削除ボタンを追加することができます。

サービスとしてのトースター (19:24)

APIとクライアントを密結合することなく、私たちは幅広いサービスのサポートができる一般的なアプリケーションを設計することができます。

サービスとしてのトースターを想像してみてください。私たちはトーストの背後にあるセマンティックな意味を理解する、トースターアプリケーションを開発することができます。トーストと解凍、どちらのコンセプトも理解します。このアプリケーションは、製造業者または機能が設定したのにもかかわらず、別のトースターAPIを挙げることができます。これは、トースターに応じて、遅延タイマーのような状態遷移を私たちに提供するかもしれません。

私たちのアプリケーションは、トースターがどのように動作するのかと同じセマンティックな意味に準拠する限り、世界のすべてのトースターで利用可能な状態遷移に基づいて、そのURIに適応させることができます。すべての状態遷移情報を持っているので、APIからの属性に基づいて実行時にフォームを設計することができます。

登録の状態遷移はEメール、パスワード、名前、性別、誕生日、そして年齢の情報が必要だとしましょう。これらの利用可能なフィールドを取得し、バリデーションを行うことができます。サーバは、一番上のフィールドはEメールだと教えてくれ、そしてそれをバリデートする方法についてのルールも知らせてくれます。実行時にバリデーションがどのように動作するのかをダウンロードすることができます。

アプリケーションにパスワードの最大の長さをハードコーディングすると、それを変更するのがとても難しいので、そういった場合などにはとても便利なのです。ユーザーが正しいものを入力すると、それは正しいと判断され、フォーム送信のためのボタンを有効にします。

デモ (20:56)

この講演の始めに私がみなさんにお見せしたPollsというアプリケーションですが、実際に存在し、そして私はハイパードライブを使用するためにこれを設計しました。

これとAPIがどのように動作するかの簡単なデモをお見せしましょう。テストとして、これの機能を変更します。このAPIはSirenとHAL、およびAPI Blueprintで記述されたプレーンなJSONもサポートしています。ここでは、それがロードする時、もしくは「私が引っ張って更新」をした時に、APIがどのような動作をするのか、クライアントが何を行っているのかを理解しなければなりません。

インターフェースを作成するためには、この仕様書をダウンロードします。クライアントは特定の状態遷移を探し、もしそれが使用できなくなっている場合は、それをただインターフェースから隠してしまいます。私たちのクライアントは変化に適応するのです。

結論 (27:29)

ここまで、クライアントが実行時にどのように動作するのかを学び、どのように適応させることができるかを見てきました。

これはAPIクライアントを破壊することなく、開発のスピードをより速め、少ないコードを書くために使用することができます。実行時にこれをシンプルに学ぶだけなので、どのように動作するかの事前の知識がなくても、APIを使うことができます。何の不具合を産むことなく、開発のスピードを上げることができます。

本日私がお見せしたもの、ハイパードライブ、モデルオブジェクト、そしてHAL、JSON、SirenからのAPIでの状態遷移の方法の情報は、すべてオープンソースとして公開しています。

Q&A (28:17)

Q: あなたがCore DataまたはRealmにデータを保存したいとして、どうやってバージョニングなしのそれのように属性や内容を変更しますか?

Kyle: APIがあなたに、どのようにそれが動作できるのかを返すとき、それはたぶん、その時だけのことであって、将来どのように動くか、ではありません。

おそらく、APIは、私たちが最初にそれらをダウンロードした時に、質問を削除することを許可します。しかし、それは将来のケースではないかもしれません。あなたがアプリケーションをリロードしたら、あなたは、この関係性はいずれなくなるということを予期せねばなりません。しかし、それをシリアライズして返すとき、リロードしたいと思うかもしれないので、自身をリロードするための状態遷移を示すURLを持っています。もし変更が発生したら、あなたはアップデートされたバーションを手に入れるために、このURLをコールできます。

Q: マイグレーション、もしくはマイグレーションスキームのようなものと一緒だとどのように動作しますか?

Kyle: あなたは簡単に、ストラクチャー自体をデータベースにシリアライズできるでしょう。標準的なインターフェースは状態が決して変化するものではありませんので、マイグレーションが必要になってくるでしょうが、理論的には、あなたはそれを変更することができます。あなたはあるバージョンから別のバージョンへ表現を変更する方法のルールをカスタマイズしなければなりません。

Q: RESTをどのように定義しますか?私はいつもHTTPメソッドとRailsのメソッドの間のマッピングとして見てきました。しかし、これはどんなプロトコルにおいても、メソッドからメソッドへのあらゆる種類のマッピングになれるように思えます。

Kyle: RESTは、制約のセットから設計されています。その中の8個はRoy Fieldingdissertationにあると思います。私がここでカバーをした最も重要なものの1つは、HATEOASで、これは、ある状態から別の状態への状態遷移をたどることによって状態を再現するエンジンのために、ハイパーメディアを使用しています。これはたいてい誤解されています。多くの人々がこれらの制約が実際に存在することを正しく理解していないので、最近はそれらはあまりたどられません。RESTはCRUD(create, read, update, delete)に関するものではありません。そこには、あなたが実行できる様々な状態遷移があります。そしてRailsはいくらか、RESTがどういったものかについてのコンセプトを持っています。彼らは多少はそれを定義に含めています。しかしそれは実際にはRESTの定義とは異なります。

翻訳: Sohei Kitada, Sayuri Takizawa

About the content

This content has been published here with the express permission of the author.

Kyle Fuller

Kyle is a developer from the UK. He’s been working in open source for a lot of time. Software Developer. Creator of Palaver, a beautiful IRC client for iPhone and iPad. Part of the core team in open source projects such as CocoaPods, Pelican, and many others.

From his own words: “I craft beautiful applications and developer tools. Mostly focusing on iPhone and iPad. Active in many open source communities”

Website: https://fuller.li

4 design patterns for a RESTless mobile integration »

close