Freelance Orgsin Official Site

ごゆっくりしていって下さい

【キソ】Web APIの設計で気を付けていること

APIとは

Application Programming Interface の略です.簡単に言えば,ソフトウェア同士が自身でプログラムを組まなくても,特定の機能を呼び出すインタフェースを提供したもののことである.

APIは,内部で交互に通信をする場合に用いるだけでなくサーバサイドとネイティブアプリの通信に用いたり,時には外部に公開したりする.今回はそういったWebに関するHTTP通信によるAPIの設計についてである.

最近では,サーバレスアーキテクチャが注目されており,AWS利用者なら例えばAPI Gatewayをエントリポイントと見て,Lambdaで処理を行いS3やDynamoDBを変更するなどの動きを一連の流れとしていたりする.また,Cogniteを用いることでユーザ識別も可能となる.最近読んだ本では,TDDベースでサーバレス設計についてかなり初歩的な話から書かれていて誰でも読みやすいと思われる.

www.orgsin.com この記事でも読もうと思っていた本である.

サーバーレスシングルページアプリケーション ―S3、AWS Lambda、API Gateway、DynamoDB、Cognitoで構築するスケーラブルなWebサービス

サーバーレスシングルページアプリケーション ―S3、AWS Lambda、API Gateway、DynamoDB、Cognitoで構築するスケーラブルなWebサービス

とはいえ,必ず1つの手法が正しいとは思わないので要件や規模感,社内風土に合わせた設計が重要である.やはり,ユーザ体験を良くするためには,切っても切れないコストが隣合わせということである.

設計するにあたって

基本的には,業務上APIを設計しなくてはいけない事が多いので前提を省く事が多いが今回はそういう部分も含めて書いておこうと思う.

先ほど挙げたサーバレスアーキテクチャは,今回は使用しないという前提で話を進めるが使った場合でもフローは変わらずツール(API Gateway,Lambda)を使うかどうか,という話になってくると思う.

人は,どういうときにAPIが欲しいと思うのだろうか,どんなAPIなら使いやすいのか,セキュリティのことも考えないといけないのだろうか?

などと,挙げていくとたくさん出てくる.今回はこの3つについて簡単にまとめておく

こんな時にAPIが欲しい

業務上とあるデータが必要になったり,フロントサイドからJS等でユーザに関する特定の処理を追加したいなどというデータベースを介在するタイミングが多いのではないだろうか.あるいは,外部のOAuthを利用したログインを採用することも多々あり,その際にも何らかのエンドポイントは必要になってくる.

使いやすいAPIとは

プログラマが使いやすいAPIである.例えば,仕様がRFCベースで統一されていたり,GoogleやTwitterのようなAPIを提供している会社のルールを模倣していたり.ただ,他の企業を参考にする場合FacebookやLinkedin,Instagramなども参考にしてみると良い.

バージョン管理,リクエスト・レスポンスルール,認証等の仕様はどこでも大きく変わらないし自分ルールでやってしまうよりは使いやすいものになるかもしれない.

また,特定の用途限定のAPIとして,あるいは広く大勢の人に使ってもらうためのAPIとして提供するかで設計内容も変わってくる.前者の場合,汎用性を持たせすぎて逆に使いづらいAPIにならないように注意しなければならない.例えば本来1回の呼び出しで簡潔するような特定の用途限定の処理を,汎用的に作るせいで通信回数を無駄に増やしてしまうのような設計になっていたら,一度踏みとどまって考え直した方がよさそうだ.そのようなことがないからと言って安心はできない.特定の用途限定のAPIの場合,特定のクライアント依存のためちゃんと設計しないと実装がひっくり返るようなミスをしている可能性もあるので,その限定したレスポンスを吟味する重要性は増す.

セキュリティについて

セキュリティに限らないが,絶対安全という保証はない.全世界のプログラマを驚愕させたHeartbleedもまだ記憶に新しいところだと思う.GoogleがOpenSSLに脆弱性を報告してからも,対応が間に合わなかった企業に不正閲覧の事件が度々起こっているのだが,そもそもOpenSSL 1.0.1gが公開されるまで,2年と1ヶ月弱の間この脆弱性が混入していたという事実は消せない.

REST

Representational State Transfer の略で,Roy Fieldingという人が論文で提唱した.今回この記事で扱いたいのは一般的なWeb APIを作る時の参考にしたいだけでRESTについて掘り下げたいといった試みはまったくない.また,最近ではGraphQLを採用しクライアントにデータ項目を指定させる形式もGitHubなどで使われている.今回は採用しないが,世界中のプログラマに使われることを想定し,より汎用的な設計が求められているならGraphQLも候補に入れておくべきかもしれない.

エンドポイント

クライアントに提供するURIとしては,やはり使いやすくわかりやすいものを提供するのが理想的である.

例えば,ユーザ情報を扱うAPIを作るならば,HTTP1.1では以下のようなエンドポイントを用意して上げることになるだろう.ただし,POST, PUT, PATCHについては一緒くたにされていたりするので必ずしも使い分けることが最善策とは言い切れない.またRFCが更新されると仕様も変わったりするので情報収集は大切である.

GET /users・・・ユーザを取得する
POST /users・・・ユーザを登録する
PUT /users/{id}・・・特定のユーザを登録・更新する
PATCH /users/{id}・・・特定のユーザを部分的に更新する
DELETE /users/{id}・・・ユーザを削除する

リクエストメソッド

クライアントがAPIにリクエストを送る場合,メソッドを指定することになる.例えば,ユーザ情報の例を使ってみる.

cURL -XGET https://example.com/users
cURL -XPOST -d "" https://example.com/users

RFC7231はHTTP/1.1の仕様.英語で読むのが,という方は以下の日本語を見てみるといいかも.

RFC 7231 — HTTP/1.1: Semantics and Content (日本語訳)

引用すると

  • GET

[ ターゲットリソースに対する,現在の選定される表現 ]の転送を要請する

情報取得の主な仕組みであり,ほぼすべての処理能 最適化の力点が置かれる所である。

誰かが[ HTTP を介して識別し得る何らかの情報を取得する ]ことについて話すとき,たいていは GET 要請の発行を指している

  • POST

[ ターゲットリソースが,自前の特有の意味論に則って,要請内に同封された表現を処理する ]ことを要請する

[ HTML フォームに入力された一連のフィールド ]などのデータブロックを,データ取り扱い処理に提供する。

[ 掲示板, ニュースグループ, メーリングリスト, ブログ, その他同類の記事群 ]などへ,メッセージを投函する。

生成元サーバにより まだ識別されていない,新たなリソースを作成する。

既存のリソースの,いくつかの表現に、データを付加する。

  • PUT

[ ターゲットリソースの状態を,[ 要請メッセージペイロード内に同封された表現により定義される状態 ]として作成する, あるいはその状態に置換する ]ことを要請する

意図は、その正確な効果が 生成元サーバのみに既知であっても,冪等であり、中継者からは可視になる

  • DELETE

[ 生成元サーバに,ターゲットリソースと,その現在の機能性との間の結び付けを除去してもらう ]ことを要請する

部分的な更新

HTTPステータスコード

クライアントに返すHTTPステータスコードとしては,大きく分けると,以下のようになる

  • 2xx・・・成功した.
  • 3xx・・・リダイレクト.
  • 4xx・・・クライアント側でエラーが発生した.
  • 5xx・・・サーバ側でエラーが発生した.

より細かく各々のステータスを分類して返却しても良いが,基本的にはクライアントがわかればよいのでその辺りはAPIの規模感だったり要件にもよる.

ここで重要なことは,区分は最低限守るということ.そうでないと,昔僕が使ったことがあるAPIのように,クライアントから送るパラメータが不正なのに200を返して,メッセージにエラー内容を入れてあったり,4xx系なのに,5xxを返してきたせいでデバッグの時間が余計にかかったことがある.これでは,HTTPステータスコードを見て処理を行う場合に非常に使いづらいし,汎用的な処理を適用できなくなる.

バージョン管理

これについては様々議論のわかれるところだが,個人的にはバージョン管理はするべきだと思う.また,その場合バージョンをどう表現するかでも意見は割れやすい.よく見かけるのは以下である.

  1. バージョン管理はしない
  2. /v1, /v2 のようにメジャーバージョンのみを含めるタイプ
  3. /v1.1.1 のような,セマンティック バージョニングをURIにも採用する形式をとっている.
  4. /20170618 のように日付を含めるタイプ
  5. そもそもURIは変えずにパラメータに加えるタイプ
  6. 全てのメソッドがPOST送信であり,Bodyになんらかのキーやハッシュ値でバージョンを管理しているタイプ

個人的な意見で申し訳ないところだが,先程も言ったように1度公開したエンドポイントは後方互換を保てないような仕様変更はするべきではないと思っている.というのもAPIが変わると再度クライアント側で改修をしなくてはならないためだ,というところで,1のバージョン管理をしない場合そのデメリットを理解した上で設計する必要がある.

今回6つ参考程度に挙げたが,よく採用されているのは主に2のタイプで,僕も通常2で設計する.というのも,後方互換が取れない場合にURIを変えれば良い.クライアントは任意のタイミング,あるいはこちらがサポートを終了するまで旧URIを使用し続けることができるためだ.

2とは別に3の丁寧なバージョニングは,マイナーバージョンやパッチバージョンをカウントアップした際にもエンドポイントを変更しなくてはならないので,クライアントからしたら多少不便だと感じるだろう.

4は,日付がわかりやすくてよいが,エントリポイントとして日付管理が複雑になりがちであるため,僕は採用したことがない.

5や6は,好みかもしれない.パラメータに加える形式は昔のAPIで見たことがある.どちらにしても最近主流の方法ではなさそうだ.

※あくまでもパターンの話で,特定のAPIを否定しているわけではありません.

会社毎のバージョン管理

参考程度に,2017年 7月 2日 日曜日時点の国内会社のAPIの状況を少し貼っておく.

※こちら問題があれば言ってください.すぐに修正・削除致します.

company name version management number style
rakuten API 3 /20140222/~
yahoo API 1 /V1/~
Backlog API 1 /v1/~
chatwork API 1 /v1/~
freee API 1 /1/~
hotpepper API 1 /v1/~
HackerNews API 1 /v0/~

拡張子

基本的にはJsonで良いのではないだろうか.Amazonのような大手でもXMLだったりするが,FacebookやTwitter,GoogleではJsonで通信が可能.

文字のエンコーディング等は,サーバ側で全部決めるのではなくクライアントとコンテントの交渉をするのでそのリクエストが来ても良いようにXMLの対応をしておくのも悪くない.

セキュリティ

特定の企業に限定させるなら,IP制限をかける必要があるし,通信は基本的にはSSLが良い.最近では,認可のためOAuth2が使われることが多いでしょう.OAuthに関しての説明は書けば書くほど怪しいものになりそうなのでここでは書かない.また,リミットも重要で,1分間で何回アクセス可能だ,とか1日で数万回がマックスとか,そういう制限はかけたほうが良い.

最後に

どんなアーキテクチャを採用するにせよ,ソフトウェアに必要最低条件を盛り込むのは当然として,それ以上に「あるべき姿」も理解した上で取捨選択し,最適解を見つけていきたい.負の遺産というものは,プログラマが現時点で分かっている以上に将来的に発生するものだと思うし,全てを盛り込むことはおよそ不可能だと考えられる.別の優秀なプログラマに,「なんでここがこうなっているの?」と聞かれた時に妥当な回答ができるようにしないといけない.例えば「将来」自分の妥当な答えが「変化した」から「ソフトウェアを変えよう」と方針を決めることができる.