WEB API The Good Parts 3章の自分的まとめ
前回からの続きで、今回は以下の本の3章を自分的にまとめたいと思う。
- 作者: 水野貴明
- 出版社/メーカー: オライリージャパン
- 発売日: 2014/11/21
- メディア: 大型本
- この商品を含むブログ (7件) を見る
3. レスポンスデータの設計
各サブの章を一言でまとめられた。
エラー部分については重要だなぁ、と感じたため、しっかり解説。
データフォーマット
→ JSONをサポートすべき。それ以外(xml、MessagePackなど)はオプション。
データフォーマットの指定方法は以下の3つ。記号はおすすめ度。- ○ クエリパラメータ
- × 拡張子
- ○ Acceptヘッダ
レスポンスの内容をユーザが選べるようにする
そのままの意味。選べる値はフィールドごとか、グループ単位かのいずれかで、その情報をクエリパラメータに付与する。
http://api.example.com/v1/users/12345?fields=name,age
エンベロープは必要か
メタデータを含んだ形ですべてのAPIが同じデータ構造を返すために実際のデータをくるむための構造をエンベロープと呼ぶ。
HTTPヘッダがメタ情報を持っているため、エンベロープは不要。データはフラットにすべきか
不要な階層化はするべきではないが、階層化すべきものはする。
たぶん開発時は普通にオブジェクトをつくれば、それで階層化すべきものはされ、不要に階層化していない形になると思う。配列とフォーマット
トップレベルに配列を包んだオブジェクトを置くか、直接配列を置くか。
筆者としてはオブジェクトで包んだほう推し。理由は以下のよう。- レスポンスデータが何を示しているものかがわかりやすくなる
- レスポンスデータをオブジェクトに統一することができる
- JSONインジェクションを防ぎ、セキュリティ上のリスクを避けることができる
各データの名前
前回と同じ考え方。大きな整数とJSON
32bit を超える数値は丸め込み誤差が発生するため、文字列を使うべき。
3.6 エラーの表現
3.6.1 ステータスコードでエラーを表現する
HTTP のステータスコードを正しく使って表現すること。
ステータスコード | 意味 |
---|---|
100番台 | 情報 |
200番台 | 成功 |
300番台 | リダイレクト |
400番台 | クライアントサイドに起因するエラー |
500番台 | サーバサイドに起因するエラー |
3.6.2 エラーの詳細をクライアントに返す
上記のステータスコードだけではわからないので、レスポンスボディにエラー専用のデータ構造を用意して、エラーの詳細を返す。
{ "error" : { "code": 2013, "message": "Error Description", "info": "Detail Description or its url" } }
3.6.4 エラーの際にHTMLが返ることを防ぐ
例えば存在しないURL を叩いた場合にWEBサーバ側の設定でHTML が返ってしまうことがないようにしっかり設定する。
つまり、API だけでなく、その周りのミドルウェアの設定も必要。
3.6.5 メンテナンスとステータスコード
メンテナンスでAPI を停止する場合は503 のHTTPステータスコードを返却し、Retry-After のヘッダで再開の時刻を知らせるべき。
じゃあどうやって実装するの?
レスポンスの内容をユーザが選べるようにする
今回の部分であれ?どうやって実装するんだろう?って思ったのは、
とくに「レスポンスの内容をユーザが選べるようにする」部分だ。
実装イメージがわかなかったので調べたみた。
実際に実装するのはJava、Spring Boot。
こういうのはやっぱりStack OverFlow に書いてあるね!
参考にしたのは以下。
json - Spring rest api filter fields in the response - Stack Overflow
Model クラスの作成
ここではlombok を使用している。
@JsonFilter(RestControllerAdvice.PROFILE_FILTER) @Data public class Profile { private String firstName; private String lastName; private LocalDate birthDay; private String gender; }
ここで重要なのはJsonFilter
アノテーションを付与していること!
これでfilter 対象であることを表している。
Controller クラスの作成
Profile
クラスを作成して、値を全部つめて返却する簡単なController
クラス。
@RestController public class ProfileController { @RequestMapping(value = "/api/v1/users/me/profile", method = RequestMethod.GET) public Profile getV1Profile() { Profile profile = new Profile(); profile.setFirstName("太郎"); profile.setLastName("山田"); profile.setBirthDay(LocalDate.of(1986, 1, 1)); profile.setGender("MALE"); return profile; } }
Advice クラスの作成
リクエストに詰められたfields
は一括してAdvice
クラスで処理するようにする。
@ControllerAdvice(annotations = RestController.class) public class RestControllerFilterAdvice extends AbstractMappingJacksonResponseBodyAdvice { public static final String PROFILE_FILTER = "PROFILE_FILTER"; @Override protected void beforeBodyWriteInternal(MappingJacksonValue bodyContainer, MediaType contentType, MethodParameter returnType, ServerHttpRequest request, ServerHttpResponse response) { // fields パラメータの取得 String fieldsParamter = ((ServletServerHttpRequest) request).getServletRequest().getParameter("fields"); String[] fields = fieldsParamter == null ? new String[0] : fieldsParamter.split(","); // Filter の設定 FilterProvider filters = new SimpleFilterProvider().addFilter(PROFILE_FILTER, fields.length == 0 ? SimpleBeanPropertyFilter.serializeAllExcept(fields) : SimpleBeanPropertyFilter.filterOutAllExcept(fields)); bodyContainer.setFilters(filters); } }
まずAbstractMappingJacksonResponseBodyAdvice
クラスをextends
する。
このクラスを継承することでレスポンスボディの変更が楽に行える。
beforeBodyWriteInternal
メソッドだけコーディングすればよくなるのだ!
レスポンスのフィールドを指定するのはFilterProvider
クラスを作成して、
MappingJacksonValue
のフィルターに指定すればよい。
リクエストにfields
パラメータがあるかないかでfilter のメソッドを変更している。
これはパラメータがない場合には、全フィールドを返却するためである。
GitHub
今回作ったものはGitHub にあげておくので、参考にしてくださいねー。
所感
今回学んだ、こうあるべきって内容については普通に身についていた。
一つどうやって実装するんだろうってイメージがつかめない部分があったが、
実際にコーディングしてみて意外と簡単に実装できるものだなぁ、と感じた。
重要なのは概念を理解すること、そして実装イメージをもつこと!