当前位置: 代码迷 >> 综合 >> Scala ZIO 的 Module Pattern 应用
  详细解决方案

Scala ZIO 的 Module Pattern 应用

热度:77   发布时间:2023-09-23 10:00:02.0

ZIO 的 Module Pattern

Module Pattern 1.0

让我们通过编写一个Logging服务开始学习这种模式:

  1. Bundling 捆绑 —— 定义一个为模块提供名称的对象,这可以(不一定)是一个包对象。我们创建一个logging对象,所有的定义和实现都将包含在这个对象中。
  2. Service Definition 服务定义 —— 然后我们创建Logging伴生对象。在伴生对象中,我们使用名为Servicetrait来定义服务定义。特质是我们定义服务的方式。服务可以是与具有单一责任的一个概念相关的所有东西。
  3. Service Implementation 服务实现 —— 之后,我们通过创建一个新服务来实现我们的服务,然后使用ZLayer.succeed构造函数将整个实现提升为ZLayer数据类型。
  4. Defining Dependencies 定义依赖关系 —— 如果我们的服务依赖于其他服务,我们应该使用ZLayer.fromServiceZLayer.fromServices这样的构造函数。
  5. Accessor Methods 访问器方法 —— 最后,为了创建更符合人体工程学的 API,最好为我们所有的服务方法编写访问器方法。

效果 = effect = 副作用
访问器方法允许我们通过 ZIO 环境利用服务中的所有功能。这意味着,如果我们调用log,我们不需要从 ZIO 环境中取出log函数。 serviceWith方法帮助我们每次访问效果环境并减少冗余操作。

type Logging = Has[Logging.Service]// 伴生对象的存在是为了保存服务定义和实时实现
object Logging {
    trait Service {
    def log(line: String): UIO[Unit]}val live: ULayer[Logging] = ZLayer.succeed {
    new Service {
    override def log(line: String): UIO[Unit] =ZIO.succeed(println(line))}}
}// Accessor Methods
def log(line: => String): URIO[Logging, Unit] =ZIO.serviceWith(_.log(line))

我们可能需要控制台和时钟服务来实现日志服务。在这种情况下,我们使用ZLayer.fromServices构造函数:

object logging {
    type Logging = Has[Logging.Service]// 伴生对象的存在是为了保存服务定义和实时实现object Logging {
    trait Service {
    def log(line: String): UIO[Unit]}val live: URLayer[Clock with Console, Logging] =ZLayer.fromServices[Clock, Console, Logging.Service] {
    (clock: Clock, console: Console) =>new Logging.Service {
    override def log(line: String): UIO[Unit] =for {
    current <- clock.currentDateTime_ <- console.printLine(s"$current--$line").orDie} yield ()}}}def log(line: => String): URIO[Logging, Unit] =ZIO.serviceWith(_.log(line))
}

这就是 ZIO 服务的创建方式。当我们使用log方法时就会被要求必须提供Logging layer。

在编写应用程序时,我们并不关心将哪个实现版本的Logging服务注入到我们的应用程序中,最终,它将通过诸如provide之类的方法提供。

Module Pattern在zim中的应用

介绍了官网基本例子来自己实现一个真实需求。现在需要实现一个redis服务,使用zio-redis
作为客户端为我们的zim定义一个redisCacheService

/*** Redis缓存服务** @author 梦境迷离* @version 1.0,2022/1/10*/
object redisCacheService extends ZimServiceConfiguration {
    // trait Has[A]与ZIO环境一起用于表示效果对类型A的服务的依赖性。// 例如,RIO[Has[Console.service],Unit]是一种需要控制台的效果的服务。在ZIO库中,提供类型别名作为常用服务的简写,例如:`type Console = Has[ConsoleService]`type ZRedisCacheService = Has[RedisCacheService.Service]object RedisCacheService {
    //服务声明定义trait Service {
    def getSets(k: String): IO[Nothing, Chunk[String]]def removeSetValue(k: String, v: String): IO[Nothing, Long]def setSet(k: String, v: String): IO[Nothing, Long]}// 服务实现定义lazy val live: ZLayer[RedisExecutor, Nothing, ZRedisCacheService] =// fromService对env的类型也多个Tag,功能与fromFunction相同ZLayer.fromFunction {
     env =>new Service {
    override def getSets(k: String): IO[Nothing, Chunk[String]] =redis.sMembers(k).returning[String].orDie.provide(env)override def removeSetValue(k: String, v: String): IO[Nothing, Long] =redis.sRem(k, v).orDie.provide(env)override def setSet(k: String, v: String): IO[Nothing, Long] =redis.sAdd(k, v).orDie.provide(env)}}}def getSets(k: String): ZIO[Any, RedisError, Chunk[String]] =ZIO.serviceWith[RedisCacheService.Service](_.getSets(k)).provideLayer(redisLayer)def removeSetValue(k: String, v: String): ZIO[Any, RedisError, Long] =ZIO.serviceWith[RedisCacheService.Service](_.removeSetValue(k, v)).provideLayer(redisLayer)def setSet(k: String, v: String): ZIO[Any, RedisError, Long] =ZIO.serviceWith[RedisCacheService.Service](_.setSet(k, v)).provideLayer(redisLayer)
}

非最佳实践,为了在tapir处理时使用unsafeRun,而该方法需要环境(此时存在依赖循环),所以这里直接调用provideLayer,后面使用unsafeRun的时候就不需要再提供环境了,官方给出的访问器方法并不使用provideLayer,而是在程序最外层将环境传递进去。

在zim中,最外层环境主要为akka:

object ZimServer extends ZimServiceConfiguration with zio.App {
    private lazy val loggingLayer: URLayer[Console with Clock, Logging] =Logging.console(logLevel = LogLevel.Debug,format = LogFormat.ColoredLogFormat()) >>> Logging.withRootLoggerName("ZimApplication")private val program: ZIO[Logging, Nothing, ExitCode] =(for {
    routes <- ApiConfiguration.routes_ <- AkkaHttpConfiguration.httpServer(routes)} yield ()).provideLayer(ZimEnv) .foldM(e => log.throwable("", e) as ExitCode.failure,_ => UIO.effectTotal(ExitCode.success))override def run(args: List[String]): ZIO[zio.ZEnv, Nothing, ExitCode] =program.provideLayer(loggingLayer)}

ZimEnv是zim程序的主要环境,但不包括redis,因为redis的API我们使用了provide手动提供了环境依赖。

loggingLayer是什么?这是将配置与zio-redis提供的layer组合后的完整的redis服务所需的新的layer:

  protected lazy val redisLayer: ZLayer[Any, RedisError.IOError, ZRedisCacheService] = RedisCacheConfiguration.live >>> RedisCacheService.live

这表明,我们将配置的layer给redis服务的layer,以构建出新的layer。由于配置不再依赖其他layer,所以最终我们的layer是能构建出来的。(可达的)

zim 是一个基于 scala 和 zio 实现的 web im。

什么是zim

zim 名字取自 zio 和 IM 的结合体。

zio 是一个 scala 中用于异步和并发编程的类型安全、可组合的库

IM 为即时通信系统

简单来说,zim 是一个主体基于 zio 的 web 应用,zim 会尽可能使用 zio 所提供的功能和相关库。

zim更多详情 https://bitlap.org/zh-CN/lab/zim

  相关解决方案