Scala ZIO 的 Module Pattern 应用

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值