zio1 升级到 zio2 踩坑和总结
并不全,记录了一些流程和注意点。新项目建议直接用 zio2!
首先,从 1.0 迁移到 2.0,可以使用官方的 scalefix 规则完成一部分方法自动替换(迁移主要解决方法重命名,去掉Has
)。
然后,添加依赖到 plugins.sbt:addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "<version>")
然后,执行迁移:sbt "scalafixEnable; scalafixAll github:zio/zio/Zio2Upgrade?sha=series/2.x"
,这会完成大部分关于方法名的重写。 比如:之前含有effect
的方法被重写为带有attempt
,带有M
的被重写为带有ZIO
。
不过仍有一些方法是被删除的没有修正,或者遗漏的一些方法没有被重写,需要自己手动改了,基本不需要什么大的改动,删除的方法可以在官方迁移文档中找到,实在找不到可以到discord
频道询问。
接着更新 zio 办法到 2.0.0 即可。这里不用直接更新到最新版,这样可以保证迁移是最小改动,迁移后再升级即可。所有生态库也需要升级,如果有的生态库不支持,就暂时不能升级。
对于业务系统,当我们执行上述命令后,其实我们已经完成了大部分迁移。最后,我们应该尝试编译项目,修复剩余的编译错误。通常这步必会报错,因为由于 2.0 已经删除了Has、ZEnv、ZManaged
,迁移规则也并不是完善的。 执行迁移命令后,Has
被直接删掉了,代码看起来更清爽。
在删除ZManaged
后,官方发现迁移工作可能非常庞大,后来出了个过渡方案,允许暂时不迁移ZManaged
,但是需要导入一个中间包:"dev.zio" %% "zio-managed" % "<2.x version>"
zio2 全部使用Scope
,所以ZManaged
本身不在核心库了。如果打算直接迁移,把ZManaged[Any, E, A]
改成ZIO[Scope, E, A]
即可。同时把resource.use(f)
改成ZIO.scoped { resource.flatMap(f) }
。 之前ZManaged
的acquireRelease
相关方法都已经在ZIO
中,toManaged_
也需要删掉,返回的R
类型多出一个Scope
,返回类型从ZManaged[R, E, A]
变成ZIO[R with Scope, E, A]
(此时需要ZIO.scoped()
才能使用)。
Clock、Console、Random、System
这些基础Layer
已经移动到顶级包下面,需要改导入语句。
个人认为变动最大的是Transducer
:拿一个 zio-redis 解码器举例,它在 1.0 中是这么写:
final val decoder: Transducer[RedisError.ProtocolError, Byte, RespValue] = {
import internal.State
val processLine =
Transducer
.fold[String, State](State.Start)(_.inProgress)(_ feed _)
.mapM {
case State.Done(value) => IO.succeedNow(value)
case State.Failed => IO.fail(RedisError.ProtocolError("Invalid data received."))
case other => IO.dieMessage(s"Deserialization bug, should not get $other")
}
Transducer.utf8Decode >>> Transducer.splitLines >>> processLine
}
到了 2.0 是这么写:
final val decoder = {
import internal.State
// ZSink fold will return a State.Start when contFn is false
val lineProcessor =
ZSink.fold[String, State](State.Start)(_.inProgress)(_ feed _).mapZIO {
case State.Done(value) => ZIO.succeedNow(Some(value))
case State.Failed => ZIO.fail(RedisError.ProtocolError("Invalid data received."))
case State.Start => ZIO.succeedNow(None)
case other => ZIO.dieMessage(s"Deserialization bug, should not get $other")
}
(ZPipeline.utf8Decode >>> ZPipeline.splitOn(internal.CrLfString))
.mapError(e => RedisError.ProtocolError(e.getLocalizedMessage))
.andThen(ZPipeline.fromSink(lineProcessor))
}
这里ZTransducer
被重写为了ZPipeline
,并且使用方式有些变化,不是改个名字就能编译的,甚至调用方还需要略微改动。
现在ZSink
和ZStream
都是基于ZChannel
实现,现在设计更合理,解码器decoder
是由输入流经过ZPipeline
处理再到输出: ZStream
=> ZPipeline
=> ZSink
。
相比之前的ZTransducer
,ZPipeline
更容易理解。同时ZTransducer
并不够通用,在流式解码中性能并不好,所以被弃用了。