Spring5.0 + KotlinではDoma、Request Interceptorあたりはどうなっているのか調べてみた
Spring5.0のリリースが迫るなか、プロジェクトへ導入に向けて色々と調べている。インタセプターなどのSpring Frameworkにおける作法はどうなっているか、便利に使えていたライブラリとの相性はどうなのか、などをアウトプットしていく。
次の2つのはてな?
を調べてみた。
- DBアクセスにはDomaを使いたい。Spring Transactionalの
@Transactional
も合わせて使える? HandlerInterceptorAdapter
や@ControllerAdvice
などAOPがRouterFunctionで使いたいけど、どうすれば?
それでは1つずつ整理していく。
Domaとの相性
Spring Boot 1.5系とDomaの相性は良くSpring Transactionalの@Transactional
も問題なく使えている。
@Transactional
の使い勝手は良くアノテーションを付けたメソッド無いでRuntimeExceptionが発生すればロールバックしてくれる。
@Transactional override fun invoke(command: CreateTaskCommand): Task { return taskRepository.create(command.title).fold({ task -> task }, { error -> throw handle(error) }) }
com.mysql.jdbc.ReplicationDriver
のドライバを使い @Transactional(readOnly = true)
をつければスレーブに対してDBアクセスが行われる。
DomaのKotlinサポートの対応方法をベースにSpringBoot 2.0で試してみたところ、問題なく使えた!
Domaとの相性は問題ないと結果が得られたのでDBアクセスには引き続きDomaを使っていく。
動いているコードはgithubにあるので合わせて参照ください。
RouterFunctionでリクエストやExceptionを良しなに処理したい
注意!FilterFunctionを用いたエラーハンドリングは正しくありません。Exceptionをエラーハンドリングしたい場合は WebExceptionHandler
を使います。
詳細は次のエントリにまとまっています。
HTTPリクエストをハンドリングするアプリケーションの関心事として次のようなものがある。
- HTTPリクエストをインターセプトして共通の処理を入れたい
- エラーレスポンスの扱いを共通化したい
これらの関心事をSpring5.0から導入されるRouterFunctionでは、どのように解決すれば良いか。
Spring4系であればHTTPリクエストのインターセプトにはHandlerInterceptorAdapter
を実装したクラスを用意すれば良かった。サービス層で発生したエクセプションに応じてエラーレスポンスを定義するクラスを@ControllerAdvice
のアノテーションをつけたクラスを用意すれば良かった。
RouterFunctionでは、FilterFunction
をつかって2つの関心事を解決できる。
コードとしては次のようになる。
@Configuration class TaskRoutes(private val taskHandler: TaskHandler, private val exceptionFilter: ExceptionFilter) { @Bean fun taskRouter() = router { (accept(APPLICATION_JSON) and "/api").nest { "/task".nest { POST("/", taskHandler::create) GET("/{id}", taskHandler::fetchByTaskId) PUT("/{id}", taskHandler::updateByTaskId) DELETE("/{id}", taskHandler::deleteByTaskId) PUT("/{id}/finish", taskHandler::finishByTaskId) } "/tasks".nest { GET("/", taskHandler::fetchAll) } } }.filter(exceptionFilter()) } @Component class ExceptionFilter { private val logger = KotlinLogging.logger {} operator fun invoke(): (request: ServerRequest, next: HandlerFunction<ServerResponse>) -> Mono<ServerResponse> = { request, next -> try { next.handle(request) } catch (e: Exception) { when (e) { is SystemException -> status(e.status).json().body(Mono.just(ErrorItem(e.message ?: "web application error", e.status.value().toString(), null))) else -> { logger.error(e) { "unknown exception: %s".format(e.message ?: "unknown error") } status(HttpStatus.INTERNAL_SERVER_ERROR).json().body(Mono.just(ErrorItem(e.message ?: "internal server error", null, null))) } } } } }
.filter(exceptionFilter())
の箇所でFilter Functionを定義している。
コードの例ではExceptionFilter
クラスでは各APIで発生したExceptionをフィルタリングしている。Exceptionが発生した場合はエラーレスポンスを返すようにしている。これで例外処理をハンドリングできるインタセプターをRouterFunctionに追加できる。
Spring4系で HandlerInterceptorAdapter
と@ControllerAdvice
で分かれていたところがFilterFunction
に集約された格好である。
コード
コードを書きながら調べたのでgithubと合わせて確認いただけます。
まとめ
- Domaとの相性が問題ないことやHTTPハンドリングの関心事もRouterFunctionでも解決できることが分かった。
- 並行してSpringFoxとの相性も調べている。APIドキュメントの快適さもSpring4系を使う恩恵があったのでSpring5.0からも問題なく使えることを期待している。