概要
今作っているアプリで実際に起きた問題ですが、GET エンドポイントの query parameter が多くなってきてしまった問題です!
検索機能を作ったときに、いろんなパラメーターを query parameter としてサーバーサイドに送らないといけなくなって、 query parameter が多すぎて code smell checker ( Sonar Qube ) に怒られました…
最初は簡素な検索機能だったのに、改善を重ねるごとに検索したい項目がどんどん増えてその度に query parameter が増えていき、Controller のメソッド引数がとんでもない数になっていました。
ということで、対処法を調べたので、SpringBoot と比較しながら報告させいただきます。
SpringBoot の場合
SpringBoot だと query parameter を管理するクラスを作ればいいんじゃね?ということで以下のコントローラーと query parameter を管理するクラスを作ってみました。
@RequiredArgsConstructor
@RestController
public class BookController {
private final BookService bookService;
@GetMapping(path = "/book/search")
@ResponseStatus(HttpStatus.OK)
public BookResponse searchBook(SearchRequest request) {
System.out.println(request);
List<Book> list = bookService.search(request);
return BookResponse.of(list);
}
}
Controller では、book/search というエンドポイントにGETでアクセスできるようにしています。引数としてSearchRequestを定義しています。クラスにまとめる前は、ここにパラメータの数だけ引数が増えていくという感じでした。
とても読みにくいし、なんのパラメータがあったのかも忘れてしまう状態でした。
そこで、下記のように SearchRequest でまとめてみました。
@AllArgsConstructor
@NoArgsConstructor
@Data
public class SearchRequest {
private String parameter1;
private String parameter2;
private String parameter3;
private String parameter4;
.
.
.
}
この状態で、実際に GET のエンドポイントに parameter=aaa としてアクセスしてみると以下のような結果になって、しっかり request が受け取れてることが確認できたと思います。
このような実装をすることで、引数が大量になって見にくくなることもなくなりました。SpringBoot はただクラスにまとめるだけなので実装は簡単ですね。
Quarkus の場合
Quarkus でもやってみようと思います。
そもそも SpringBoot と同じやり方でいけるのかな?ということでとりあえずほとんど同じ状態を以下の二つのクラスで実装してみました。
@ApplicationScoped
@Path("/book")
public class BookController {
@Inject
BookService bookService;
@GET
@Path("/search")
@Produces(MediaType.APPLICATION_JSON)
public BookResponse searchBook(SearchRequest request) {
System.out.println(request);
List<Book> list = bookService.search(request);
return BookResponse.of(list);
}
}
@AllArgsConstructor
@NoArgsConstructor
@Data
public class SearchRequest {
private String parameter1;
private String parameter2;
private String parameter3;
private String parameter4;
.
.
.
}
この状態で、実際に GET のエンドポイントにアクセスしてみると以下のような結果になりました。
そう簡単にはいかないみたいです…
エラーを呼んでみると NotSupportedException が出てしまいました。。。このクラスを read できないみたいですね。
Quarkus の解決方法
SearchRequest を Bean に登録せよということかなと思い、色々調べてみました。
そこで、@BeanParam というアノテーションをつけてみることになりました。
@ApplicationScoped
@Path("/book")
public class BookController {
@Inject
BookService bookService;
@GET
@Path("/search")
@Produces(MediaType.APPLICATION_JSON)
public BookResponse searchBook(@BeanParam SearchRequest request) {
System.out.println(request);
List<Book> list = bookService.search(request);
return BookResponse.of(list);
}
}
これで、今度こそ行けると思い実行してみました。すると以下の結果が出ました。
エラーは無くなりましたが、parameter が null になっていて、正確な値を取得できていません!
さらに調べていくと、SearchRequest 内の各パラメータに @QueryParam というアノテーションを入れる必要があるとのことだったので、入れてみた。
@AllArgsConstructor
@NoArgsConstructor
@Data
public class SearchRequest {
@QueryParam("parameter1")
private String parameter1;
@QueryParam("parameter2")
private String parameter2;
@QueryParam("parameter3")
private String parameter3;
@QueryParam("parameter4")
private String parameter4;
.
.
.
}
これで実行してみると…
見事! query parameter を受け取ることに成功しました!
まとめ
SprintBoot と Quarkus で query parameter を管理するクラスの作成方法が異なっていました!
ただ、クラス管理ではなくて、メソッドの変数に直接書く場合は同じやり方で行けるっぽいです。
結論、メソッドの引数である query parameter を管理するクラスに @BeanParam というアノテーションをつけて、その中のフィールドに @QueryParam というアノテーションをつければ解決しました!
コメント