Стрелка Котлина Либо и транзакции

#java #spring #kotlin #transactions #spring-transactions

Вопрос:

Я пытаюсь использовать объект библиотеки стрелок Котлина Either для обработки исключений в рамках проекта.

Мой опыт работы с этим до сих пор был в порядке, но я изо всех сил пытаюсь найти способ обработки транзакций Either и, в частности, отката. Весной бросок a RuntimeException -это верный способ заставить транзакцию откатиться. Однако при использовании Either не создается исключение и, следовательно, не запускается откат.

Вы можете рассматривать это как многогранный вопрос:

  1. Either Подходит ли это для правильного Exception обращения? Не альтернативный поток управления, я имею в виду истинные ситуации ошибок, когда поток программы необходимо остановить.
  2. Если да, то как вы добиваетесь отката с их помощью?
  3. Если ответ на вопрос 2. заключается transactionManager в программном использовании — не могли бы вы этого избежать?
  4. Я втисну это, как ты избегаешь вложений Either ?

Ответ №1:

На некоторые из ваших вопросов нет прямых ответов, но я сделаю все возможное 😀

  1. Либо подходит для истинной обработки исключений. Не альтернативный поток управления, я имею в виду истинные ситуации ошибок, когда поток программы необходимо остановить.

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

Если вы предпочитаете использовать Either API, вы, вероятно, можете обернуть API Spring на основе исключений с Either<RuntimeException, A> помощью или Either<E, A> .

Поэтому, чтобы ответить на ваш вопрос, Either подходит для обработки исключений. Однако, как правило, вы будете улавливать только интересующие вас исключения и моделировать их с помощью своего собственного домена ошибок. Неожиданным исключениям или исключениям, которые вы не можете разрешить, часто разрешается всплывать.

  1. Если да, то как вы добиваетесь отката с их помощью?

Пример псевдокода обертывания transaction: () -> A с transactionEither: () -> Either<E, A> .

 class EitherTransactionException(val result: Either<Any?, Any?>): RuntimeException(..)

fun transactionEither(f: () -> Either<E, A>): Either<E, A> =
  try {
     val result = transaction { f() }
     when(val result) {
       is Either.Right -> result
       is Either.Left -> throw EitherTransactionException(result)
     }
  } catch(e: EitherTransactionException) {
     return e.result as Either<E, A>
  }
 

И теперь вы должны иметь возможность использовать Either<E, A> , сохраняя при этом модель Spring, основанную на исключениях, нетронутой.

Если ответ на вопрос 2. заключается в программном использовании TransactionManager — не могли бы вы этого избежать?

Я ответил на вопрос 2, уже избегая его. В качестве альтернативы, используя transactionManager программное обеспечение, вы могли бы избежать необходимости создавать это исключение и восстанавливать значение.

Я втисну это в себя, как вам избежать вложенности в них

  • Используйте Either#flatMap или either { } для цепочки зависимых значений (Either<E, A>) (A) -> Either<E, B>
  • Используется Either#zip для объединения независимых значений. Either<E, A> Either<E, B> (A, B) -> C .

Ответ №2:

1. Подходит ли то или другое для истинной обработки исключений?

Несколько самоуверенный ответ: Обычная обработка ошибок с помощью исключений имеет недостатки, аналогичные недостаткам использования страшного оператора GOTO: выполнение переходит к другой, конкретной точке вашего кода-оператору catch. Это лучше, чем GOTO, в том смысле, что он не возобновляет выполнение в совершенно произвольной точке, а должен идти вверх по стеку вызовов функций.

С помощью модели обработки ошибок Либо<Ошибка, значение> вы не прерываете поток программы, что облегчает обработку кода, облегчает отладку и тестирование.

Как таковой, я бы не только сказал, что это уместно, но и лучше.

2. Если да, то как вы добиваетесь отката с их помощью?

Я предлагаю использовать шаблон транзакции Springs: Пример:

 fun <A, B> runInTransaction(block: () -> Either<A, B>): Either<A, B> {
  return transactionTemplate.execute {
    val result = block()
    return@execute when (result) {
      is Either.Left -> {
        it.setRollbackOnly()
        result
      }
      is Either.Right -> result
   }
}!! // execute() is a java method which may return nulls

fun usage(): Either<String, String> {
  return runInTransaction {
    // do stuff
    return@runInTransaction "Some error".left()
  }
}
 

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

Вам также потребуется предоставить табличку транзакции классу, содержащему этот метод.

3. Если ответ на вопрос 2. заключается в программном использовании TransactionManager — не могли бы вы этого избежать?

Я не понимаю, как, поскольку декларативное управление транзакциями Springs построено на модели обработки ошибок исключений и ее прерывании потока управления.

4. Я втисну это, как вы избегаете вложенности в них?

Вы можете использовать Either.fx { } , чтобы избежать любого из вложений. Обратите внимание на ! синтаксис для привязки значений Либо к области .fx. Это, так сказать, «распаковывает» их:

 fun example(): Either<Error, Unit> {
  return Either.fx {
            val accessToken: String = !getAccessToken()
            return@fx !callHttp(accessToken)
        }
}

fun getAccessToken(): Either<Error, String> {
  return "accessToken".right()
}

fun callHttp(token: String): Either<Error, Unit> {  
  return Unit.right()
}
 

Чтобы это сработало, все левые значения должны быть одного типа. Если левое значение привязано, оно будет возвращено. Это позволяет избежать вложенных операторов when или функциональной цепочки с картой/плоской картой/сгибом и т. Д.