😎

Web APIとは?

2024/10/21に公開

はじめに

今回はWeb APIについての記事を書いていきます。
私自身、APIの設計や開発はほぼ経験がないのですが、知識としていずれ必要になるかと思い、「Web API: The Good Parts」を読みました。
今回はその内容のアウトプットを中心に書いていこうかと思います。

Web APIとは

そもそもWeb APIとは何なのかというところから説明していきます。
まず、APIとは、"Application Programming Interface"の略で、ソフトウェアコンポーネントの外部インターフェイス、つまりは機能はわかっているけどその中身の動作は詳しくわからない(知らなくても良い)機能のカタマリを、外部から呼び出すための仕様のことを指します。そのAPIの中でもWeb APIとは「HTTPプロトコルを利用してネットワーク越しに呼び出すAPI」とこの本では定義されています。
プロトコルとしてHTTPを使用するため、エンドポイントはURIによって指定されます。

APIには特定のサービス内で使用するものもあれば世界的に公開されているものもあります。
APIを公開することで、さまざまな付加価値を他の企業や個人が提供してくれるようになり、サービスの価値や情報の質が上がる可能性があります。

Web APIの代表例

この項では、実際に公開されているAPIの例を紹介したいと思います。
具体例を知ることで、Web APIがどのようなものかイメージしやすくなるかと思います。

X(Twitter)

X(Twitter)が公開しているAPIでは、外部のアプリケーションから、タイムラインを取得したり、いいねやポストなどの操作を行うことができます。

Google

GoogleのAPIでは、GmailやGoogleDriveなど、Googleが提供しているプラットフォームやサービスを外部から扱うことができます。
具体例として、Google Maps APIを利用することで自社のWebサイト上にGoogleMapを表示したり、自社の店舗への経路を出すことができます。

Amazon

AmazonAPIを活用すると、出品情報の更新や削除などの管理作業を行えたり、商品の支払情報を取得したりなど、さまざまな機能を利用できます。
例えばAmazonが提供しているOrders APIを利用することで、自社システム上から新規受注の一覧を取得したり、発送済みやキャンセル注文のステータス管理を行ったりできます。

Web APIの設計・開発時に意識すべきこと

この項では、Web APIの設計・開発時に意識すべきこととして、本の各章の内容を簡単にまとめていきたいと思います。

エンドポイントの設計

まず、APIとしてどの機能を提供するかを決め、エンドポイントを考えながら整理していきます。
Web APIにおけるエンドポイントとは、APIにアクセスするためのURIのことを意味します。
そのため、良いURIの設計を考えていくことが必要です。一般的にURIにおいて重要な事柄として下記のような点が挙げられます。

  • 短く入力しやすい
    短くて入力しやすい=シンプルで覚えやすい。
  • 人が読んで理解できる
    何を目的としたものなのかがある程度わかる。
  • 大文字小文字が混在していない
    大文字小文字が混在していると分かりづらく、間違えやすいものになる。基本的には小文字で統一する。
  • サーバ側のアーキテクチャが反映されていない
    サーバ側のシステム構成や言語などはユーザーには関係ないため、反映すべきでない。
  • ルールが統一されている
    基本的にWeb APIは複数のエンドポイントを持つため、利用する単語やURIの構造などのルールを統一しておいた方がわかりやすい。

Web APIではURIと合わせて、GET/POSTなどのHTTPメソッドで"何をするか"を指定します。
例を挙げると下記表のような形になります。

目的 エンドポイント メソッド
ユーザー一覧取得 http://api.〇〇.com/v1/users GET
ユーザーの新規登録 http://api.〇〇.com/v1/users POST
特定ユーザーの取得 http://api.〇〇.com/v1/user/:id GET
特定ユーザーの削除 http://api.〇〇.com/v1/user/:id DELETE

表の例で情報の取得や登録などのエンドポイントは用意できましたが、様々な状況への対応はできていません。例えば、ユーザーが1万人いて、エンドポイントを叩くと、1万人全てが取得される場合、データサイズが大きすぎます。さらに巨大なサービスになると、もはやAPIとして機能するとは思えません。そのため、1度に取得可能な人数の上限を決め、ページングを行ってデータを取得できるようにする必要があります。また、ユーザーの検索を行いたいといった状況には、絞り込みのパラメータを実装することで検索を行うことができます。これらの場合に利用するのがクエリパラメータです。

例)クエリパラメータ例
http://api.〇〇.com/v1/users?page=10
http://api.〇〇.com/v1/users?name=taro

また、SNSなどのログインが必要なサービスのAPIを設計する場合にはログイン周りのAPIについても考える必要があります。現在のWeb APIにおいて非常に広く一般的に利用されているのはOAuthという仕様です。

例)OAuthのエンドポイント例
http://api.〇〇.com/v1/oauth/token

ここまで情報取得や登録など個々の機能のAPI設計を見てきましたが、http://api.〇〇.com/v1/の部分の設計も考えておく必要があります。api.〇〇.comのようにサービスのホスト名にapiという名前を入れてしまった方が、シンプルでどのサービスのapiかがわかりやすくなりますし、ホスト名を分けることでアクセスをDNSレベルで分割できるので管理がしやすいなどのメリットもあります。

この項で解説してきた内容は広く一般に公開し、多くの人に使ってもらうためのAPI向けでしたが、利用するターゲットがより限られてくる場合は、汎用性やわかりやすさよりもユーザー体験が重要になってきます。例えば、特定のアプリケーションでしか使用しないAPIの場合、ホーム画面を表示するために様々なAPIにアクセスする必要があると、画面表示に時間がかかってしまい、ユーザーを待たせてしまいます。これは良いユーザー体験とは言えないため、1画面で表示する情報を1つに詰め込んだ"ホーム画面表示用"APIを作成し、それに1回アクセスするだけですべての情報が取得できる方が利便性が高いです。
このようにどれくらいの人に利用されるかによって設計方法なども変わってきます。

レスポンスデータの設計

次にWeb APIへリクエストをした際に返されるレスポンスデータの設計について見ていきます。
最初に考えるべきはどういったデータフォーマットで返すかということです。ここは、基本的にはJSONに対応し、必要があればXMLに対応するというのが現実に即しています。よく使われているAPIを見てもJSONに対応していないAPIはかなり少数派となっています。JSONが広まった主な理由としては、JSONの方がXMLよりシンプルで同じデータを表すのにサイズが小さくてすむこと、そしてウェブの世界においてはクライアントのデフォルト言語であるJavaScriptと相性がとても良いことが挙げられます。

もし、JSON以外のフォーマットもサポートしたい場合は、クライアントに取得したい形式を指定させる方法も考慮する必要があります。一般的には下記のような方法が使われます。

  • クエリパラメータを使う方法
  • 拡張子を使う方法
  • リクエストヘッダでメディアタイプを指定する方法

データフォーマットが決まったら、実際にどんなデータを返すか決めていくことになります。
レスポンスデータでまず考えるべきことは、APIのアクセス回数がなるべく減るようにすることです。そのためにはAPIのユースケースをきちんと考えることが重要になってきます。
バックエンドのテーブル構造を反映する必要はなく、アプリケーションの特性を踏まえた上で、利用者が使いやすい構造を検討するべきです。
例えばSNSの友人一覧を取得するAPIでユーザーIDだけを返されたとしても、利用者は他の情報も取得したいと考え、他のAPIにもアクセスする必要があります。このような場合は、以下のようなユーザー情報が入ったリストにした方が使いやすさは向上します。

{
    "friends": [
        {
            "id": 111111,
            "name": "sato"
            "age": 24
            ・・・
            ・・・
        },
        {
            "id": 111111,
            "name": "sato"
            "age": 24
            ・・・
            ・・・
        },
        ・
        ・
        ・
    ]
}

APIの利用方法を利用者に委ねた場合、思いがけない使われ方をすることもありますが、レスポンスで取得する項目を利用者が選択可能にすることで対策もできます。
また、JSONやXMLでは階層構造を表すこともできますが、なるべく不要な階層構造はなくし、フラットにした方がデータサイズも小さくできるため良いでしょう。階層化が適している場合は階層構造にするのも良いです。

続いて書くデータ項目の名前についてですが、これについてはエンドポイントの設計時のポイントと重複する部分もありますが、下記のような考え方が良いかと思います。

  • 多くのAPIで一般的に利用されている単語を用いる
  • なるべく少ない単語数で表現する
  • 単語の連結方法をAPI全体で統一する
  • 変な省略形はしない
  • 単数系・複数形に気をつける

最後にエラーの表現に関してですが、なるべく多くの情報を返し、利用者が問題を解決してAPIを利用できるようにする必要があります。
真っ先にやるべきことは適切なステータスコードを使うことです。合わせてエラーの詳細な内容を返すことも重要になってきます。エラー情報として返すべき情報は、エラーの詳細コード(API提供側で独自に定義したコード)やエラーメッセージ、ドキュメントの関連ページへのリンクなどが挙げられます。
また、エラー時にHTMLが返却されてしまうこと防いだり、APIのメンテナンスなど停止時の対応なども考慮する必要があります。

HTTPの仕様を利用する

Web APIはHTTP上で通信を行うので、HTTPの仕様をしっかりと理解して、それを活用した方がより使い勝手が良いものとなります。標準の仕様を利用して作られたAPIは利用者も理解しやすく、バグの混入を減らしたり、既存のライブラリやコードを再利用できたりと、より使いやすいものになります。
この項ではHTTPの仕様と利用する方法について話していきます。
HTTPはリクエストとレスポンスで構成されており、それぞれにヘッダとボディがあります。ヘッダにはメタ情報、ボディにはデータが入ります。
レスポンスヘッダの先頭には必ずステータスコードが入っています。上でも述べましたが、ステータスコードは適切に返した方が、利用者がエラーを正しく認識してくれる可能性が高まります。

続いて、キャッシュとHTTPの仕様についてです。キャッシュはユーザー体験にも両者のコストにも大きく影響を与えるため、可能な限り有効活用すべきです。
HTTPのキャッシュにはExpiretion Model(期限切れモデル)とValidation Model(検証モデル)という2つのタイプがあります。

  • 期限切れモデル
    あらかじめレスポンスデータに保存期限を決めておき、期限が切れたら再度アクセスをして取得を行います。いつ期限が切れるかをサーバからのレスポンスに含めて返すことで実現できるのですが、HTTP1.1ではそのための方法が2種類用意されています。1つはCache-Controlレスポンスヘッダを使う方法、もう1つはExpiresレスポンスヘッダを使う方法です。
Cache-Control: max-age=3600
Expires: Thu, 01 Jan 2025 00:00:00 GMT
  • 検証モデル
    今保持しているキャッシュが最新であるかを問い合わせて、データが更新されていた場合にのみ取得を行います。こちらは条件付きリクエストに対応する必要があります。クライアントから「過去に取得したある時点でのデータ」に関する情報を送り、更新されていた時にのみデータを返し、更新されていなかったら304("Not Modified")というステータスコードを返します。
    最終更新日かエンティティタグのどちらかをサーバ側で生成し、レスポンスヘッダに含まれてクライアントに送信され、クライアントはそれをキャッシュとともに保持します。
    最終更新日付とエンティティタグはそれぞれ、Last -ModifiedとETagというレスポンスヘッダを使って返します。
    クライアントが条件付きリクエストを行う際、最終更新日付を使う場合にはIf-Modified-Sinceヘッダを、エンティティタグを使う場合はIf-None-Matchヘッダを使います。

もし、APIの性格上キャッシュをさせたくない場合は、Cache-Controlヘッダを以下のようにすることで明示的に「キャッシュして欲しくない」と伝えることもできます。Cache-Controlヘッダには他にもキャッシュをどのように行えば良いかを示すための情報を指定することができます。

Cache-Control: no-cache

HTTPのリクエスト、レスポンスでは送信するデータ本体の形式を表すためにメディアタイプを指定する必要もあります。レスポンスではContent-Typeというヘッダを利用してメディアタイプを指定します。

Content-Type: application/json
Content-Type: image/png

リクエストの際にはContent-TypeとAcceptの2つのヘッダが利用されます。Content-Typeはレスポンスヘッダと同様、リクエストボディがどんなデータ形式で送られているのかを示します。一方Acceptは、クライアントが「どんなメディアタイプを受け入れ可能か」をサーバに伝えるために利用します。

この項ではさまざまなHTTPヘッダを首魁してきましたが、独自のHTTPヘッダを定義することも可能です。ヘッダ名を決める際には、"x-"という接頭辞をつけたり、サービスやアプリケーション、組織の名前をつけるといったことが一般的になっています。命名はURIやレスポンスデータの命名を参考につけると良いでしょう。サービスを通して、独自ヘッダの命名ルールを統一することが最も重要です。

設計変更しやすいAPIを作る

Web APIは何らかのアプリケーションのインターフェイスとしての役割を持ちます。そしてそのアプリケーションは公開後、機能の強化やバグの修正など状況に応じて変化していきます。その影響でWeb APIも変更しなければならない場合があります。しかし、変更の影響はAPIを利用している外部のサービスやシステムにも与える可能性があるため、APIの変更は非常に大変です。
APIの利用者によって状況は異なりますが、APIの変更にはさまざまな問題が発生する危険性があるため、問題が発生する可能性を減らすための方法をまとめていきます。

方法の一つとして、APIをバージョンで管理するという方法があります。APIを新しいアクセス形式で公開することで、古い形式でアクセスしてきているクライアントに対してはそれまでと変わらないデータを送り、新しい形式でのアクセスには、新しい形式のデータを返すことができます。つまり、複数のバージョンのAPIを提供するということです。
一番わかりやすいのは、異なるURIでAPIを公開する方法です。例えば、下記URIのようにURIの中にバージョンを埋め込むとわかりやすいです。

http://api.〇〇.com/api/v1

バージョンの番号に関しては、整数でカウントアップするのが一般的であり、適している方法です。
整数1つで表す = メジャーバージョンアップの際にだけAPIのバージョンを上げますよということを意図しています。なぜかというと、APIのバージョンはそうそう簡単にあげるべきものではないからです。
複数のAPIをメンテナンスするにはコストがかかりますし、利用者から見てもわかりにくいため、小さな変更はなるべくバージョンを上げることなく、後方互換性を担保して対応し、後方互換性を失ってもよいと判断できるほどの大きな変更を行いたい時のみ、バージョンを上げるべきです。

バージョンの指定方法にはURIに含める以外にもクエリ文字列に含める方法や、メディアタイプで指定する方法もあります。

続いて、APIの提供を終了する場合について説明していきます。古いバージョンのAPIを提供し続けるのは、運用側のコスト増につながるため、どこかのタイミングで古いバージョンを廃止する必要が出てきます。
APIの提供を終了する際に、突然提供を終了すると、そのAPIを使用していたアプリケーションがエラーになったり、ページの一部が表示されないなど、大きな問題が起こるため、提供を終了する事前に終了日時をアナウンスしてそれまでにバージョンアップの対応をしてくれるように周知を徹底しなければなりません。

対策としてあらかじめ、提供終了時の仕様を盛り込んでおくことも可能です。例えば、公開終了したAPIにアクセスした際にステータスコード410と提供を終了した旨を伝えるメッセージを返却するように実装したり、スマートフォンアプリでは、強制的にアップデートするようにするなどです。
また、利用規約にサポート期限を明記しておくことも対策になります。例えば、新しいバージョンが提供されてから最低1年後に古いバージョンのサポートを終了するといったことを記載しておきます。

堅牢なAPIを作る

Web APIはWebアプリケーションと同様、HTTPを通じて公開されるサービスになるため、セキュリティや安定性についても考慮する必要があります。

まずセキュリティに関して、Web APIは今やいたるところで使われており、その中には、外部に知られたくない個人情報や機密情報、あるいはサービス提供側の想定しないやり方でデータの操作をされたくない、といったケースは数多くあります。そのため、インターネットでアクセス可能なWeb APIにおいては、例えば悪意のある第三者による攻撃や情報の漏洩を防ぐ、認証されたユーザーによる不正な操作を防ぐなど、さまざまな防衛策を考えておく必要があります。

サーバーとクライアント間での情報の不正入手を防ぐために、HTTPS通信を利用し、通信を暗号化して対策したり、XSSやXSRF、JSONハイジャックなど悪意のある攻撃に対してそれぞれに対策するなど、セキュリティに考慮した設計にする必要があります。
また、ユーザーによる不正なアクセスについても対策が必要です。例えば、パラメータを改ざんすることで本来取得できない情報を取得したり、サーバ側のデータをありえない値に変更するといったことが挙げられます。対策として、サーバ側でチェックし、アクセスを禁止するようにします。また、課金アイテムの購入処理や、対戦ゲームの勝敗などはクライアント側から送られてきた情報の整合性をサーバ側でもチェックするようにすると安全性が高まります。
他にも、リクエストの再送信による支払いの偽装などもユーザーの不正アクセスの例として挙げられます。この場合は、1回の購入につき1回の処理だけ行うようにチェックするといった形で防ぐことが可能です。

セキュリティの強化のためにブラウザで利用されているヘッダもあります。いくつか紹介したいと思います。

  • X-Content-Type-Options
    ファイル形式を他のファイル形式として解釈することを防いでくれます。例えば、JSONファイルがHTMLと勝手に解釈されてXSSなどが発生するという脆弱性を防止するために有効です。
    X-Contec-Type-Options: nosniff
    
  • X-XSS-Protection
    XSSの検出、防御機能を有効にするヘッダです。(すべてのXSSパターンを検出できるわけではありません)
  • X-Frame-Options
    指定したページがフレーム(FRAMEとIFRAME要素)内で読み込まれるかどうかを制御することができるヘッダ。
  • Content-Security-Policy
    読み込んだHTML内のIMG要素、SCRIPT要素、LINK要素などの読み込み先としてどこを許可するのかを指定するためのヘッダ。
  • Strict-Transport-Security
    このヘッダを利用すると、あるサイトへのブラウザからのアクセスをHTTPSのみに限定させることができます。
  • Public-Key-Pins
    SSL証明書が偽造されたものでないかをチェックするために利用します。ヘッダ内には証明書の内容のハッシュ値と有効期限が書かれており、それ以降のアクセスの際に、そのハッシュ値を使って証明書が正しいものであるかどうかを判別するようになっています。

最後に、安定性の部分について書いていきます。
ネットワーク上に公開されているサービスは、外部からの大量のアクセスを受けるというリスクに常に晒されています。大量のアクセスを受けると、やがてサーバが負荷に耐えられなくなり、誰もサーバに接続できない状態となります。

大量のアクセスがやってきてしまう問題の解決策として、最も現実的な方法は、ユーザーごとのアクセス数を制限することです。つまり、単位時間あたりの最大アクセス回数(レートリミット)を決め、それ以上のアクセスがあった場合にエラーを返すようにします。
レートリミットを行うにあたっては、下記のような点を考慮する必要があります。

  • 何を使ってユーザーを識別するか
  • リミット値をいくつにするか
  • どういう単位でリミット値を設定するか
  • リミットのリセットをどういうタイミングで行うか

これらはサービスの特性によって変わってきます。
制限は緩くしすぎて大量のアクセスを許してもいけないですし、きつくしすぎて利用者が不便に感じてもいけないので、提供するAPIがどのようなケースで利用されるのかをできる限り考えた上で、決める必要があります。

おわりに

とても長くなりましたが、ここまで読んでいただきありがとうございます🙇‍♂️
冒頭にも記述した通り、私自身APIの設計や開発はほぼ経験がないため、イメージしにくい部分もありましたが、内容はAPIの入門書としてとても良いかと思いました。本の中では実際のAPIの事例などを用いて、API設計や開発について説明されています。今後、Web APIの設計・開発を行う際に再度読み、さらに理解を深めたいと思いました。

Discussion