본 문서에 관하여
- ZIO의 error handling 방식을 함수 위주로 정리하여 실제 Scala에서 error handling 전략을 살펴본다
ZIO는 기본적으로 아래와 같이 Input, Error, Output Type을 정의해야 한다.
ZIO[R, E, A]
Java와 다른 점은 실질적인 Return type이 Error와 실제 Value 를 정의한다는 것이다.
아래는 Error를 명시적으로 구현한 코드라 볼 수 있다.
val aFailedZIO: IO[String, Nothing] = ZIO.fail("Something went wrong")
val failedWithThroable: IO[RuntimeException, Nothing] = ZIO.fail(new RuntimeException("Bumb!"))
// RuntimeException의 Error Type을 String으로 변환
val failWithDescription: ZIO[Any, String, Nothing] = failedWithThroable.mapError(_.getMessage)
그러면 Java의 try-catch 처럼 명시적 혹은 비명시적으로 발생한 Error를 어떻게 처리할 것인가?
// 잘못된 사용 예시
val badZIO: ZIO[Any, Nothing, RuntimeFlags] = ZIO.succeed {
println("Trying something")
val string: String = null
string.length
}
// attempt 함수를 사용하여 Throwable을 error를 처리할 수 있다.
val anAttempt: ZIO[Any, Throwable, Int] = ZIO.attempt {
println("Trying something")
val string: String = null
string.length
}
// catchAll과 catchSome을 활용한 catch error
val catchError: ZIO[Any, Throwable, Any] =
anAttempt.catchAll(a => ZIO.attempt(s"Returning a different value because $a"))
val catchServiceErrors: ZIO[Any, Throwable, Any] = anAttempt.catchSome {
case e: RuntimeException => ZIO.succeed(s"Ignoring runtime excep[tion: $e")
case _ => ZIO.succeed("Ignoring everything else")
}
위의 코드와 같이 성공인지 실패인지 알 수 없는 코드를 실행하는 경우 ZIO.attempt 를 활용하고,
catchAll과 catchSome을 활용하여 발생한 exception에 전체에 대해 처리가 가능하다.
Scala의 일반적은 Error 처리 기법인 Try, Either, Option 등이 ZIO에도 동일하게 사용된다.
// Option / Try / Either to ZIO
val aTryToZIO: ZIO[Any, Throwable, Int] = ZIO.fromTry(Try(42 / 0))
// either -> ZIO
val anEither: Either[Int, String] = Right("Success!")
val anEitherToZIO: ZIO[Any, Int, String] = ZIO.fromEither(anEither)
// ZIO -> ZIO with Either as the value channel
val eitherZIO: URIO[Any, Either[Throwable, Int]] = anAttempt.either
// reserve
val anAttempt_v2 = eitherZIO.absolve
// option -> ZIO
val anOption: ZIO[Any, Option[Nothing], Int] = ZIO.fromOption(Some(42))
// implements
def try2ZIO[A](aTry: Try[A]): Task[A] = aTry match {
case Failure(exception) => ZIO.fail(exception)
case Success(value) => ZIO.succeed(value)
}
def either2ZIO[A, B](anEither: Either[A, B]): ZIO[Any, A, B] = anEither match {
case Left(value) => ZIO.fail(value)
case Right(value) => ZIO.succeed(value)
}
def option2ZIO[A](anOption: Option[A]): ZIO[Any, Option[Nothing], A] = anOption match {
case Some(value) => ZIO.succeed(value)
case None => ZIO.fail(None)
}
def zio2zioEither[R, A, B](zio: ZIO[R, A, B]): ZIO[R, Nothing, Either[A, B]] = zio.foldZIO(
error => ZIO.succeed(Left(error)),
value => ZIO.succeed(Right(value))
)
def absolveZIO[R, A, B](zio: ZIO[R, Nothing, Either[A, B]]): ZIO[R, A, B] = zio.flatMap {
case Left(e) => ZIO.fail(e)
case Right(v) => ZIO.succeed(v)
}
계속..