SpringFoxのFailed to start bean 'documentationPluginsBootstrapper'を対処する
現象
Spring Bootで書いたWebアプリケーションにSwaggerを導入するため、SpringFoxを依存関係(build.gradle
)に含めた。
implementation "io.springfox:springfox-boot-starter:3.0.0"
アプリケーションを起動したところ、documentationPluginsBootstrapper
Beanがスタートできない旨のエラーが発生し、アプリケーションが起動できなくなった。
org.springframework.context.ApplicationContextException: Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerException: Cannot invoke "org.springframework.web.servlet.mvc.condition.PatternsRequestCondition.getPatterns()" because "this.condition" is null ...
環境
- Java 17
- SpringBoot 2.6.x、2.7.x
上記以外の環境では確認していないが起こる可能性あり。
対処法
application propertyに以下のプロパティを加える。
application.properties
の場合↓
spring.mvc.pathmatch.matching-strategy = ANT_PATH_MATCHER
application.yml
の場合↓
spring: mvc: pathmatch: matching-strategy: ANT_PATH_MATCHER
参考
StepVerifierでオブジェクトをテストしたいときはassertNextを使う
Spring WebFluxで登場するMonoやFluxをテストする際にはStepVerifierを使用する。
Mono<String>
をテストする場合、以下のようにexpectNext
メソッドを使うことでテストできる。
Mono<String> actual = Mono.just("test"); String expect = "test"; StepVerifier.create(actual) .expectNext(expect) .verifyComplete();
一方でオブジェクトをテストする場合にexpectNext
は不適である。
class User { // コンストラクタなどは省略 private final String name; private final int age; }
上のようにUserクラスを定義し、Monoのテストを書くとき、
Mono<User> actual = Mono.just(new User("name", 20)); User expect = new User("name", 20); StepVerifier.create(actual) .expectNext(expect) .verifyComplete();
ではフィールドの検証ではなくポインタの検証になってしまうため、このテストは落ちてしまう。
この場合はassertNext
を使用し内部でassertThat().usingRecursiveComparison()
を使用することで、フィールド同士を比較してくれる。
Mono<User> actual = Mono.just(new User("name", 20)); User expect = new User("name", 20); StepVerifier.create(actual) .assertNext(t -> assertThat(t).usingRecursiveComparison().isEqualTo(expect)) .verifyComplete()
Web API: The Good Partsから得たAPIレスポンス設計の知見
Web API: The Good Partsは2014年にオライリージャパンから出版された本で、Web APIの設計や開発、運用について解説した本である。全6章から構成されていて、第3章はAPIのレスポンスデータの設計について解説されている。ここでは本書を読んだ上で積極的に採用したいと思った設計指針を、私自身の意見も織り交ぜて列挙する。
データフォーマットはJSONに
該当ページ:P65 3.1 データフォーマット
構造化データを表現するデータフォーマットはJSONやXMLがあげられる。以下の点からJSONを採用する方がよいとしている。
意見
- JSONでは物足りなくなるほど複雑になってしまった場合、一度設計に立ち返る方がよさそう
フィールドはキャメルケース(camelCase)に
該当ページ:P86 3.4 各データのフォーマット
JSONがベースとしているJavaScriptの命名規則において、キャメルケースの利用がルール付けされているケースが多いから
キャメルケースがよい。
意見
- スネークケース(snake_case) vs キャメルケースは終わらない議論で、どちらも一長一短
- スネークケースの方が可読性が高いという意見がある→同意
- そもそも複数単語を連結することのないシンプルな命名が望ましいのかもしれない
- 大事なのはどのフォーマットで書くかを統一することで、採用するフォーマットを決めたら必ずそれに従うようにすれば何でもよい
レスポンスデータはフラットに
該当ページ:P79 3.3.3 データはフラットにすべきか
データをはなるべきフラットに保ち、階層化すべきところでは階層化する。 また本書でも触れられている通り、データの階層化についてGoogle JSON Style Guide[1]では、
"Data elements should be "flattened" in the JSON representation. Data should not be arbitrarily grouped for convenience."
が指針として示されている。
[1]Google JSON Style Guide#Flattened data vs Structured Hierarchy
意見
- 無駄な階層化はデータの生成、パースともに実装を複雑化させることに繋がるので避けるべき。
- 適切な階層化はデータの可読性を向上させるので行いたい。
- 適切な階層化は複数単語を連結することのないシンプルな命名に繋がりそう
配列データはオブジェクトで包む
該当ページ:P81 3.3.4 配列とフォーマット
配列データを返す場合、配列をそのまま返すかオブジェクトで配列を包むかの2通りが考えられる。
〇配列をそのまま返す例
[ { "id": 1, "name": "hoge", }, { "id": 2, "name": "piyo", }, ... ]
〇オブジェクトで包む例
{ "users": [ { "id": 1, "name": "hoge", }, { "id": 2, "name": "piyo", }, ... ] }
オブジェクトで包む場合、データをオブジェクトに統一できるほか、セキュリティ上のリスク(JSONインジェクション)を避けることができる。これらの点からオブジェクトで包む方が推奨される。
意見
- 実装する上でもオブジェクトで包んだデータの方が扱いやすい気がするので、周りを説得する上でありがたい指針
性別(gender)データは文字列で返す
該当ページ:P88 3.4.2 性別のデータをどう表すか
genderフィールドは数値で1(男性)、2(女性)と表すより、文字列で"male"や"female"と表現する。将来genderの種類が増えることを想定すると文字列の方が妥当。
意見
- ほかのEnum型データはどのように表すべきなのかも知りたい
- LSUDsをターゲットとするAPIなら文字列がよさそう
- LSUDsでもSSKDsでも、将来拡張する可能性を考慮する場合は文字列の方が便利そう
- 逆にどういった場合に数値などに置換する表現が推奨されるか知りたい
- 可読性とか実装上での使いやすさといった観点の指針も欲しい
まとめ
Web API: The Good Partsの第3章から得た知見として以下の指針を取り上げた。
- データフォーマットはJSONに
- フィールドはキャメルケース(camelCase)に
- レスポンスデータはフラットに
- 配列データはオブジェクトで包む
- 性別(gender)データは文字列で返す
本書はレスポンス設計だけでなく、エンドポイント設計や保守性、セキュリティに考慮した設計なども取り上げられているので、後々そこから得た知見も記事にしたい。