Spring @Retryable не работает с Kotlin suspend fun

Вопрос или проблема

Я пытаюсь добавить логику повторных попыток в свой метод @Service, и это не работает, если метод является suspend. (Это проект Kotlin/Gradle/Spring). Ниже приведен работающий метод (только для теста):

@Retryable(maxAttempts = 3, backoff =  Backoff(delay = 1000) )
fun getPerson(): Person {
    val random = (1..2).random()
    return runBlocking {

        println("random=$random,  ${if (random == 1) "исключение" else "успех"} ")

        if (random == 1) throw IllegalStateException("Произошло исключение")

        Person("Имя")
    }
}

А метод с suspend не работает:

@Retryable(maxAttempts = 3, backoff =  Backoff(delay = 1000))
suspend fun getPersonSuspend(): Person {
    val random = (1..2).random()
    println("random=$random,  ${if (random == 1) "исключение" else "успех"} ")
    if (random == 1) throw IllegalStateException("Произошло исключение")

    return Person("Имя")

}

Это нормальное поведение или я что-то упускаю? Весь пример здесь.

Ответ или решение

Проблема: @Retryable не работает на Kotlin suspend функции

При внедрении механизма повторных попыток с использованием аннотации @Retryable в вашем проекте на Kotlin с использованием Spring, вы столкнулись с проблемой, когда такая аннотация не работает для suspend функций. Рассмотрим, почему это происходит и как можно решить данную проблему.

Причины проблемы

Аннотация @Retryable из Spring предполагает, что методы, к которым она применяется, будут выполняться в контексте Spring AOP (Aspect-Oriented Programming). Однако функции с модификатором suspend в Kotlin не могут быть адекватно перехвачены аспектами AOP. Это связано с тем, что suspend функции, по своей природе, предполагают использование корутин, которые работают немного иначе, чем обычные функции.

Когда вы вызываете метод, помеченный как @Retryable, Spring ожидает, что этот метод будет выполнен в определенном контексте и сможет повторно вызывать метод при возникновении исключений. Однако, поскольку suspend функции требуют специальной обработки с использованием корутин и CoroutineScope, возможности AOP не могут корректно взаимодействовать с ними.

Возможные решения

  1. Wrap the Suspend Function:
    Один из способов обойти эту проблему заключается в том, чтобы обернуть вашу suspend функцию в обычный метод. Таким образом, @Retryable будет применена к обычному методу, который в свою очередь будет вызывать suspend функцию.

    Пример:

    @Retryable(maxAttempts = 3, backoff = Backoff(delay = 1000))
    fun getPerson(): Person {
       return runBlocking {
           getPersonSuspend()
       }
    }
    
    suspend fun getPersonSuspend(): Person {
       val random = (1..2).random()
       println("random=$random,  ${if (random == 1) "exception" else "success"} ")
       if (random == 1) throw IllegalStateException("Exception occurred")
    
       return Person("Name")
    }
  2. Использовать Coroutine Retry:
    Вместо использования @Retryable, можно внедрить собственную логику повторных попыток непосредственно в suspend функцию, используя корутины. Таким образом, вы будете полностью контролировать процесс повторных попыток.

    Пример:

    suspend fun getPersonSuspend(maxAttempts: Int = 3, delay: Long = 1000): Person {
       repeat(maxAttempts) { attempt ->
           try {
               val random = (1..2).random()
               println("attempt: $attempt, random=$random,  ${if (random == 1) "exception" else "success"} ")
               if (random == 1) throw IllegalStateException("Exception occurred")
    
               return Person("Name")
           } catch (e: Exception) {
               if (attempt == maxAttempts - 1) throw e
               delay(delay)
           }
       }
       throw IllegalStateException("Max attempts exceeded")
    }

Заключение

Таким образом, применение аннотации @Retryable к suspend функциям в Kotlin не работает из-за особенностей работы AOP в контексте Spring с корутинами. Наилучшим обходным путем будет обертывание suspend функции в обычный метод или внедрение логики повторных попыток непосредственно в ваши корутины.

При необходимости дополнительного обсуждения или помощи, вы можете обратиться в сообщество разработчиков или изучить документацию по Spring и Kotlin для получения более детальной информации о совместимости этих технологий.

Оцените материал
Добавить комментарий

Капча загружается...