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