1. 概要

例外処理は、RESTAPIを構築するときにカバーする最も重要なトピックの1つです。 Springは、APIの応答をカスタマイズするのに役立つネイティブ機能を使用して、例外を処理するための優れたサポートを提供します。

このチュートリアルでは、Kotlinを使用してSpring BootRESTAPIで例外処理を実装するためのいくつかのアプローチについて説明します。

2. RESTコントローラーとテンプレートメッセージ

2.1. RESTコントローラーの例

まず、サンプルAPIを作成する必要があります。 ケーススタディでは、記事を作成、更新、取得するためのサービスを公開する小さなRESTコントローラーを定義します。

@RestController
@RequestMapping("/articles")
class ArticleController(val articleService: ArticleService) {

    @PostMapping()
    fun createArticle(@RequestBody title: String): ArticleModel {
        return articleService.createArticle(title);
    }

    @GetMapping()
    fun getArticle(@RequestParam id: String): ArticleModel {
        return articleService.getArticle(id);
    }

    @PutMapping()
    fun updateArticle(@RequestParam id: String, @RequestParam title: String): ArticleModel {
        return articleService.updateArticle(id, title);
    }
}

2.2. カスタムテンプレートメッセージ

次に、APIのエラーメッセージのテンプレートを定義します。

class ErrorMessageModel(
    var status: Int? = null,
    var message: String? = null
)

statusプロパティは、HTTPステータスコードの番号を格納し、messageプロパティは、スローされた例外を説明するために定義されたカスタムメッセージを表します。

3. 例外コントローラーのアドバイス

アノテーション@ControllerAdviceを使用して、複数のコントローラーのグローバルハンドラーを定義できます。 たとえば、IllegalStateExceptionタイプのエラーに対してカスタムメッセージを返したい場合があります。

@ControllerAdvice
class ExceptionControllerAdvice {

    @ExceptionHandler
    fun handleIllegalStateException(ex: IllegalStateException): ResponseEntity<ErrorMessageModel> {

        val errorMessage = ErrorMessageModel(
            HttpStatus.NOT_FOUND.value(),
            ex.message
        )
        return ResponseEntity(errorMessage, HttpStatus.BAD_REQUEST)
    }
}

さらに、特定のシナリオのカスタム例外を定義できます。 たとえば、APIで記事が見つからない場合に例外を作成できます。

class ArticleNotFoundException(message: String) : RuntimeException(message) {
}

したがって、この例外はグローバルハンドラーによってキャッチされます。

@ExceptionHandler
fun handleArticleNotFoundException(ex: ArticleNotFoundException): ResponseEntity<ErrorMessageModel> {
    val errorMessage = ErrorMessageModel(
        HttpStatus.NOT_FOUND.value(),
        ex.message
    )
    return ResponseEntity(errorMessage, HttpStatus.NOT_FOUND)
}

最後に、APIのビジネスロジックを実行するArticleServiceクラスを定義するために必要なすべてのコンポーネントがあります。

@Service
class ArticleService {

    lateinit var articles: List<ArticleModel>

    @PostConstruct
    fun buildArticles() {
        articles = listOf(
            ArticleModel("1", "Exception Handling in Kotlin"),
            ArticleModel("2", "Decorator Patter in Kotlin"),
            ArticleModel("3", "Abstract Pattern in Kotlin")
        )
    }

    fun createArticle(title: String): ArticleModel {
        val article = (articles.find { articleModel -> articleModel.title == title })
        if (article != null) {
            throw IllegalStateException("Article with the same title already exists")
        }
        return ArticleModel("4", title)
    }

    fun getArticle(id: String): ArticleModel {
        return articles.find { articleModel -> articleModel.id == id }
            ?: throw ArticleNotFoundException("Article not found")
    }

    fun updateArticle(id: String, title: String): ArticleModel {
        val article = (articles.find { articleModel -> articleModel.id == id }
            ?: throw ArticleNotFoundException("Article not found"))
        if (title.length > 50) {
            throw IllegalArgumentException("Article title too long")
        }
        article.title = title
        return article
    }
}

4. ResponseStatusExceptionクラス

ResponseStatusExceptionクラスは、例外を動的に処理するための優れたサポートを提供します。 したがって、コントローラークラスのメソッド定義内でエラーを処理できます。

@PutMapping()
fun updateArticle(@RequestParam id: String, @RequestParam title: String): ArticleModel {
    try {
        return articleService.updateArticle(id, title);
    } catch (ex: IllegalArgumentException) {
        throw ResponseStatusException(HttpStatus.BAD_REQUEST, ex.localizedMessage, ex)
    }
}

その結果、グローバルコントローラーは必要ありませんが、特定の例外を処理する場所を定義する必要があります

5. 結論

このチュートリアルでは、Kotlinを使用してRESTAPIで例外処理を実装するためのいくつかの効率的なアプローチについて説明しました。 @ControllerAdvice アノテーションを使用してグローバルハンドラーを定義でき、ResponseStatusExceptionクラスを使用して動的ハンドラーを作成することもできます。

いつものように、この記事の完全なコードは、GitHubから入手できます。