初めに
見ていただきありがとうございます。
バックエンドエンジニアとして勤務して2年目のkmyです。
今回は初めてAPIを設計、実装する際に過去とても苦労したので自分と同じような方向けに情報をまとめられたらと思い、本記事を書きました。
私自身も経験は浅いのであくまで一例として参考にしていただけたら幸いです。
APIとは
APIとは"Application Programming Interface"の略です。
これは機能はわかるけれど実際の中身を知らなくても使える機能の塊のことです。
この塊にアクセスするためにはHTTPを用い、エンドポイントはURIによって決められます。
エンドポイントとは
APIにアクセスするためのURIを指します。
このエンドポイントにアクセスするとAPIごとに決められた返却結果を得られます。
例えば、ユーザーのマイページで書籍の一覧を取得するようなAPIがあったとすると下記のようなイメージになるかと思います。
【エンドポイントの例】 ※HTTPメソッドはgetを想定しています
http://localhost/api/mypage/user/books
このエンドポイントにアクセスすると、下記のような書籍の一覧が返却されてくるイメージです。
【返却値の例】 ※JSONを想定しています。
{
"books": [
{
"id": 1,
"title": "hoge",
"description": "hogehoge"
},
{
"id": 2,
"title": "hoge2",
"description": "hogehoge2"
},
]
}
エンドポイントの設計について
エンドポイントの原則として、以下が挙げられます。
どんな機能を持っているのかがひと目で判別できる
これだけだと漠然としすぎているのでいくつか気をつけているポイントとしては下記です。
それぞれどのような解釈をしているか書いていきます。
- 誰でも理解できること
- 表記が統一されていること
- ユースケースを表現出来ていること
1.誰でも理解できること
さきほどの書籍を取得するAPIをもとにしますが、これを試しに分解してみます。
http://localhost/api/mypage/user/books
最初にapi
とあるのでなんとなくこれはAPIであることはわかります。
次にmypage
とuser
、これはユーザーのマイページでなにかをするAPIなんだなとわかります。
最後にbooks
を追加するとユーザーのマイページで書籍を取得するAPIだと推測出来ます。
もし、これが省略された記載であったらどうでしょうか。
少し極端に書くとこのような形です。
http://localhost/api/mp/u/b
api
であることはわかりますがそれ以外はよくわからず、何かの省略だと言うことくらいしか推測できません。
短く簡潔なURIであるかもしれませんが設計者しかわからないのであれば意味がなくなってしまいます。
2.表記が統一されていること
基本的にURIには全て小文字を用いています。
このような大文字小文字が混在しているAPIはわかりづらさ、間違いを招く原因となります。
http://localhost/api/myPage/USER/books
ホスト名の部分に関しては気にしなくてもよいですが、その後に続くapi
以降で突然、記載が乱れるとAPI単位、設計者単位で粒度がまちまちになり混乱を招くので小文字で揃えたほうが良いです。
3.ユースケースを表現出来ていること
エンドポイントからユースケース(APIを使う箇所)が理解、推察出来るように設計しています。
http://localhost/api/books
例えば、上記をもとにするとapiとbooksという単語から本を扱うなにがしかのAPIだということはわかります。
ただ、ユースケースが想像できないため設計者以外がこのAPIを使いまわそうとした場合に、どのように使用してよいかわからず、間違いを招く原因になりますのでエンドポイントにユースケースがわかるような情報を詰め込むようにしています。
レスポンスデータの設計について
以下について記していきます。
1.データフォーマットについて
2.エンベロープの有無について
3.階層構造について
1.データフォーマットについて
データフォーマットはAPIの返却値の構造化データをどのような形式で返すかについてで、これは「JSON」、 「XML」のいずれかになりますが基本は「JSON」で問題ないかと考えます。
というのも、一般に公開されている多くのAPIもJSONのフォーマットに対応しているためです。
例えばTwitter、FacebookのAPIもJSONに対応していますので興味があれば公式のドキュメントを見てみてください。
2.エンベロープの有無ついて
本題のエンベロープの有無について書く前にエンベロープとは何かというと、「メタデータを含む形でAPIが一貫したデータ構造を返すように、実際のデータを包む構造」をいいます。
下記の形式だとエンベロープを用いています。
{
"header": {
"code": "0",
"message": "success"
},
"result": {
"books": [
{
"id": 1,
"title": "hoge",
"description": "hogehoge"
},
{
"id": 2,
"title": "hoge2",
"description": "hogehoge2"
}
]
}
}
エンベロープを用いない形だとこのようになるかと思います。
{
"books": [
{
"id": 1,
"title": "hoge",
"description": "hogehoge"
},
{
"id": 2,
"title": "hoge2",
"description": "hogehoge2"
}
]
}
本題に戻るとエンベロープは用いなくても問題ないと思います。
というのもHTTP通信ではもともとHTTPヘッダーの概念があり、これがエンベロープの役割と被っていて冗長になるためです。
3.階層構造について
階層構造については「可能な限りフラットにしたほうが良いけれど、状況によっては階層構造も有効」とされています。
なので、個人的にはデータが単一なのか複数かで使い分けています。
単一の場合の例を記すと下記です。
【悪い例】
{
"books": [
{
"id": 1,
"title": "hoge",
"description": "hogehoge"
}
]
}
【良い例】
{
"id": 1,
"title": "hoge",
"description": "hogehoge"
}
使い分けているポイントとしては下記です。
・階層構造が浅いほうが情報を取得しやすい
・階層構造を深くするとJSONのデータ容量が増加する
エラーの表現について
以下について記していきます。
1.ステータスコードでエラーを表現すること
2.エラー詳細を返す
1.ステータスコードでエラーを表現すること
APIを呼び出した際にはHTTPステータスコードが返却されます。
例えば、成功ですと「200」、失敗ですと対象のリソースがない「404」などがあります。
ステータスコードはこれら以外にも多くあり、それぞれに意味がありますので都度、エラーに対応したステータスコードを返却することが望ましいです。
よく使うものを参考に記します。
・400: リクエスト情報がおかしい
・403: 認証されておらず、権限がない
・404: 対象リソースが存在しない
・500: サーバー内でエラー発生した場合
・503: メンテナンス実施時
他にも多くありますので興味があればドキュメントを見ても良いかもしれません。
2.エラー詳細を返す
ステータスコードだけでエラーのカテゴリはなんとなくわかりますが、何が原因で失敗したかといったエラー詳細がわからない場合が多いです。
そのため、明確に不要な場合は除き、ステータスコードと一緒にどのような原因で失敗したのかをAPIの返却値として返してあげたほうが親切です。
例として「本の情報を登録しようとしたが、リクエストの情報がおかしくて失敗した場合」をあげます。
本を登録する画面があり、その際の登録情報として、「タイトル」、「説明」の入力が必須だったとします。
ここでタイトルを未入力で登録しようとし、ステータスコード:400で返却されるとすると「リクエスト情報がおかしいのはわかるけれど何がおかしいのかわからない」という状態に陥ると思います。
【エラー詳細無し】
ステータスコード:400
返却値:{}
ここにエラーの詳細についての返却値を追加してあげます。
【エラー詳細有り】
ステータスコード:400
返却値:{
"errors": "本のタイトルが未入力です。"
}
このようにするとタイトルが未入力で失敗したんだなとわかるので、「ステータスコード」 + 「エラー詳細」の形で返してあげたほうが親切かと思います。
まとめ
初めてAPI設計するときに知っていたら役に立ちそうなものを自分なりにまとめさせていただきました。
ここに書いていないことで考えなければいけないことも多くありますが、今回は省いています。
もし、API設計についてもっと知りたいと言う方は技術記事を探していただくか、オライリーからAPIに関する技術書が出ていますので読んでみてください。
発売されてから時間は経っていますがとても読みやすく、参考になる部分が多くオススメです。
最後まで読んでいただきありがとうございました。