•Error handling •Retry handling Agenda Functional Prerequisites: Experience with using Monad type class Sample Code: https://github.com/Hiroki6/SampleFunctionalErrorAndRetryHandling
Tagless Final with Error Handling import cats.instances.either._ implicit val eitherRepository = EitherRepository val eitherUseCase = new UseCase[EitherThrowable] eitherUseCase.update(in) Exception in thread "main" … PreconditionException: Not Found class UseCase[F[_]](implicit M: Monad[F], repository: Repository[F]) { def update(in: Input): F[Output] = { M.flatMap(repository.get(in)) { case Some(value) => repository.update(value) case None => throw PreconditionException("Not Found") } } }
MonadError type class trait MonadError[F[_], E] extends ApplicativeError[F, E] with Monad[F] { … } Type of Monad Type of Error Inherit Monad TypeClass Implemented in cats and the other functional libraries. Abstracts over error-handling monads.
Major Methods in MonadError • handleErrorWith /** * Handle any error, potentially recovering from it, by mapping it to an * `F[A]` value. * .. */ def handleErrorWith[A](fa: F[A])(f: E => F[A]): F[A] Similar to `recoverWith` method in Future and Try
Retry Handling Error has occurred on program, retry[counter=1] after 1000 [ms] sleeping..., total delay was 0 [ms] so far Error has occurred on program, retry[counter=2] after 2000 [ms] sleeping..., total delay was 1000 [ms] so far Success! Retry Retry × × ✔
• How many times to retry • How long to wait between attempts • Logging when handle the error You have to consider the following: Retry handling tends to be complicated Let’s take a look at a basic retry handling…
cats-retry Functional retry handling with cats-retry in Scala This talk is based on the article below: Scala library for retrying actions that can failed. https://github.com/cb372/cats-retry
The concept of cats-retry Action Retry Policy Error Handling • How many times to retry • How long to wait between attempts • Logging when handle the error Action with Retry Mechanism Composition of retry policy and action
Retry Policy import retry.RetryPolicies import cats.syntax.semigroup._ val policy = RetryPolicies.limitRetries[IO](3) |+| RetryPolicies.exponentialBackoff[IO](1.seconds) • ConstantDelay • FibonacciBackoff • FullJitter Delay Algorithm How many times to retry
Composing Policies Retry with a 100ms delay 5 times and then retry every minute import retry.RetryPolicies._ val retry5times100millis = limitRetries[IO](5) |+| constantDelay[IO](100.millis) val retry1mins = constantDelay[IO](1.minute) retry5times100millis.followedBy(retry1mins)
Error Handling def logError(action: String)(err: Throwable, details: RetryDetails): IO[Unit] = details match { case WillDelayAndRetry(nextDelay: FiniteDuration, retriesSoFar: Int, cumulativeDelay: FiniteDuration) => IO { logger.info( s”Error has occurred on $action, retry[counter=$retriesSoFar] after ${nextDelay.toMillis} [ms] sleeping...”) } case GivingUp(totalRetries: Int, totalDelay: FiniteDuration) => IO { logger.info( s”Giving up on $action after $totalRetries retries, finally total delay was ${totalDelay.toMillis} [ms]”) } } Logging or notification between attempts
Functional Retry Handling Cats-retry provide useful retry mechanism • Isolation between retry policy and action • Some useful delay algorithms • Composable retry policy • Can use Monad which has the instance of MonadError