ZIOの3種類のエラー
ZIOのエラーは全体で以下の3種類に大別されます。
Failures
ZIOにおける最も代表的なエラーであると言えるかもしれません。このエラーは一般的に予測されるエラーを表しています。
予測可能なエラーとなるため、このエラーについては適切なハンドリング処理を行うことが期待されています。
そのため、ZIO[R,E,A]
型の型パラメータの第二引数に現れるという特徴があります。ZIO[R,E,A]
という場合はE
になります。
具体的には以下の様な場合に発生するエラーです。
-
ZIO.attempt
内の処理でのエラー
Defects
このエラーは一般的に予測できないエラーとなります。
そのため、このエラーの型についても同様に予測できず、ZIO
型の型パラメータ上にも現れません。
具体的には以下の様な場合に発生するエラーです。
-
ZIO.succeed
内の処理でのエラー - ZIO型を返す関数の引数における処理でのエラー
Fatal
このエラーは予測できないStackOverflowError
といったJVM関係のエラーとなります。
このエラーが発生した場合、ZIOアプリケーションは強制終了するため、ハンドリング処理は行えず、出来る処理としてはエラーに関するログ出力処理のみになります。
- ZIO型を返さず、末尾再帰による最適化などがなされていない通常の再起処理における
StackOverflowError
それぞれの具体例
これからこれら3つのエラーについて、実際にエラーを意図的に発生させ、具体的に見ていきます。
Failures
このfailureエラーについては意図的に発生させる方法はいくつかあるのですが、今回はZIO.fail
関数を使った方法により発生させ、どのような挙動を示すかについて確認します。
Hello, World!
プログラムについて以下の様に改変します。
case class ApplicationServiceImpl(currentDate: Date) extends ApplicationService {
override def consoleOutput(): ZIO[Any, Throwable, Unit] =
for{
_ <- ZIO.fail(new Exception("This is a failure test."))
_ <- Console.printLine(
s"${new SimpleDateFormat ("yyyy/MM/dd HH:mm:ss").format (currentDate)} Hello, World!"
)
} yield ()
}
object MainApp extends ZIOAppDefault {
def run: ZIO[Any, Throwable, Unit] = ApplicationService.consoleOutput().provide(
ZLayer.fromZIO(ZIO.attempt {
import java.text.SimpleDateFormat
// 任意の日付文字列
val inpDateStr = "2023/07/25 17:46:00"
// 取り扱う日付の形にフォーマット設定
val sdformat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss")
// Date型に変換( DateFromatクラスのparse() )
sdformat.parse(inpDateStr)
}),
ApplicationServiceImpl.layer
).catchAll(failure => Console.printError("Failure:" + failure.toString))
}
こちらについて実行すると以下の様に出力されます。
Failure:java.lang.Exception: This is a failure test.
2023/07/25 Hello, World!
が出力されなかったのはこれを出力する処理の前にfailureエラーが発生したためとなります。
発生したfailureエラーは上層に伝播していき、catchAll
関数で最終的に処理されました。
その結果、このような出力が行われました。
catchAll
関数はfailureエラーを処理する関数になります。
このようにfailureエラーの型についてはZIO[R,E,A]
型の型パラメータの第二引数と一致する必要があります。
今回の場合ですと第二引数がThrowable
であるため、そのサブクラスであるException
型とすることができます。
Defects
次にdefectエラーを見ます。
今回はZIO.die
関数を使った方法により発生させます。
先ほどのプログラムを以下のように改変します。
case class ApplicationServiceImpl(currentDate: Date) extends ApplicationService {
override def consoleOutput(): ZIO[Any, IOException, Unit] =
for{
_ <- ZIO.die(new ArithmeticException("divide by zero"))
_ <- Console.printLine(
s"${new SimpleDateFormat ("yyyy/MM/dd HH:mm:ss").format (currentDate)} Hello, World!"
)
} yield ()
}
package application.service
import zio.ZIO
import java.io.IOException
trait ApplicationService {
def consoleOutput(): ZIO[Any, IOException, Unit]
}
object ApplicationService {
def consoleOutput(): ZIO[ApplicationService, IOException, Unit] =
ZIO.serviceWithZIO[ApplicationService](_.consoleOutput())
}
こちらについて実行すると以下の様に出力されます。
timestamp=2023-07-28T06:50:49.586017200Z level=ERROR thread=#zio-fiber-1 message="" cause="Exception in thread "zio-fiber-4" java.lang.ArithmeticException: divide by zero
at application.service.impl.ApplicationServiceImpl.consoleOutput$$anonfun$1(ApplicationServiceImpl.scala:13)
at zio.ZIO$.die$$anonfun$1(ZIO.scala:3081)
at zio.ZIO$.failCause$$anonfun$1$$anonfun$1$$anonfun$1(ZIO.scala:3155)
at application.service.impl.ApplicationServiceImpl.consoleOutput(ApplicationServiceImpl.scala:13)
at application.service.impl.ApplicationServiceImpl.consoleOutput(ApplicationServiceImpl.scala:17)
at <empty>.MainApp.run(main.scala:6)
at <empty>.MainApp.run(main.scala:19)"
Failure:
という文字列は表示されず、catchAll
関数でこのエラーが処理されなかったことがわかります。
このことからこのdefectエラーは先ほどのfailureエラーとは別の種類のエラーであることがわかります。
また、今回ZIO
型の型パラメータの第二引数をThrowable
からIOException
に変更しておりますが、ZIO.die
で発生させているエラーはArithmeticException
となっています。ところがコンパイルエラーとはならず実行自体はできています。
これは先述の通りdefectエラーは一般的に予測できない類のエラーを表しているため、型パラメータの第二引数とは直接的に関係しないためとなります。
試しに_ <- ZIO.die(new ArithmeticException("divide by zero"))
の部分を_ <- ZIO.fail(new ArithmeticException("divide by zero"))
と変更するとコンパイルエラーとなることがわかります。
catchAll
関数はfailureエラーを処理するための関数でしたが、defectエラーを処理するための関数としてcatchAllDefect
関数があります。
以下のようにプログラムを変更してください。
).catchAll(failure => Console.printError("Failure:" + failure.toString)).catchAllDefect(defect =>
Console.printError("Defect:" + defect.toString)
)
これを実行すると以下の様に出力されます。
Defect:java.lang.ArithmeticException: divide by zero
catchAllDefect
関数によりdefectエラーを処理することが出来ました。
Fatal
最後にFatalエラーです。
先ほどのプログラムについて以下の様に変更します。
case class ApplicationServiceImpl(currentDate: Date) extends ApplicationService {
override def consoleOutput(): ZIO[Any, Throwable, Unit] =
for {
_ <- ZIO
.attempt(
throw new StackOverflowError(
"The call stack pointer exceeds the stack bound."
)
)
_ <- Console.printLine(
s"${new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(currentDate)} Hello, World!"
)
} yield ()
}
trait ApplicationService {
def consoleOutput(): ZIO[Any, Throwable, Unit]
}
object ApplicationService {
def consoleOutput(): ZIO[ApplicationService, Throwable, Unit] =
ZIO.serviceWithZIO[ApplicationService](_.consoleOutput())
}
StackOverflowError
を意図的に発生させるように修正しました。また、型パラメータの第二引数の型についてもThrowable
型に戻しています。
これを実行すると以下の様に出力されます。
**** WARNING ****
Catastrophic error encountered. Application not safely interrupted. Resources may be leaked. Check the logs for more details and consider overriding `Runtime.reportFatal` to capture context.
java.lang.StackOverflowError: The call stack pointer exceeds the stack bound.
(以下省略)
出力された文字列にFailure:
もDefect:
も含まれないことからfailureエラーを処理するcatchAll
関数でもdefectエラーを処理するcatchAllDefect
関数でも処理できていないことがわかり、これらとは別種のエラーであることが確かめられました。
終わりに
今回はZIOのエラー処理について見てきました。
前章のZIOのDIについてと併せ、ZIO[R, E, A]
型の意味合いについての基本的な理解が得られたかと思います。
次章ではZIO Quill
を例としてZIOエコシステムのライブラリの利用、DB接続について見ていきます。
前章:ZIOのDIについて
次章:DB接続(ZIO Quill)
演習
- 今回の3つのエラーについて実際に動かして挙動を確かめてください。
-
Failures
の解答例は以下になります。
https://github.com/hatuda/zio-practice/tree/CHAPTER3-Failures -
Defects
の解答例は以下になります。
https://github.com/hatuda/zio-practice/tree/CHAPTER3-Defects -
Fatal
の解答例は以下になります。
https://github.com/hatuda/zio-practice/tree/CHAPTER3-Fatal
-
- 今回の方法とは別の方法でそれぞれのエラーを発生させ、挙動を確かめてください。
-
Failures
の解答例は以下になります。
https://github.com/hatuda/zio-practice/tree/CHAPTER3-Failures-Another -
Defects
の解答例は以下になります。
https://github.com/hatuda/zio-practice/tree/CHAPTER3-Defects-Another -
Fatal
の解答例は以下になります。
https://github.com/hatuda/zio-practice/tree/CHAPTER3-Fatal-Another
-