ZIO#memoize 在服务中

问题描述 投票:0回答:1

我正在设计一个 ZIO 服务。我必须在其内部逻辑中使用记忆化。我怎样才能做到这一点? 例如,如果服务的一个方法(或所有方法)需要远程主机的某种授权,我如何确保授权过程只执行一次?

trait ServiceA {
  def doSomething(question: String): Task[String]
}

class RemoteServiceA(username: String, password: String, httpClient: HttpClient) extends ServiceA {

  private val authorize: Task[Cookie] = for {
    req <- ZIO.attempt {
      HttpRequestBuilder()
        .setUri("https://.../auth/")
        .setUsername(username)
        .setPassword(password)
        .build()
    }
    resp <- httpClient.sendRequest(req)
  } yield resp.getCookie

  override def doSomething(question: String): Task[String] = for {
    authCookie <- authorize
    req = HttpRequestBuilder().setUri(s"https://.../do-something?q=$question")
    answer <- httpClient.sendRequest(req)
  } yield answer

}

现在据我了解,我不能在这里简单地使用

authorize.memoize
,它不会那样工作。我必须将授权例程移到
RemoteServiceA
类之外,并传递
memoizedAuth: Task[Cookie]
作为构造函数参数。但在我看来,这破坏了代码封装原则,因为授权过程是服务本身的内部逻辑。如果我有多个私有方法返回值,我想(懒惰地)计算一次并记住这些值以供(潜在的)将来使用,事情会变得更加丑陋。构造函数因
memoizedSomething: Task[Something]
memoizedSomethingElse: Task[SomethingElse]
而变得臃肿,并且许多内部类逻辑必须移到类外部到其他地方。

针对这种场景有更好的方法吗?

scala memoization zio
1个回答
0
投票

出于同样的原因,我也发现很难使用

memoize
。在 https://day-to-day-stuff.blogspot.com/2022/11/speed-up-zios-with-memoization.html 中,您可以找到一个在
Map 中使用 
Ref
 的记忆实现
.

Ref[Map]
仍然需要在服务本身之外构建。这就是
ZLayer
存在的原因,它们抽象了服务的生命周期。它允许在 ZIO 上下文中进行构建和销毁。

简而言之,首先你需要缓存辅助方法:

import zio._

object ZioCaching {
  implicit class ZioCachedBy[R, E, A](zio: ZIO[R, E, A]) {
    def cachedBy[B](cacheRef: Ref[Map[B, Promise[E, A]]], key: B): ZIO[R, E, A] = {
      for {
        newPromise <- Promise.make[E, A]
        actualPromise <- cacheRef.modify { cache =>
          cache.get(key) match {
            case Some(existingPromise) => (existingPromise, cache)
            case None                  => (newPromise, cache + (key -> newPromise))
          }
        }
        _ <- ZIO.when(actualPromise eq newPromise) {
          zio.intoPromise(newPromise)
        }
        value <- actualPromise.await
      } yield value
    }
  }
}

现在我们可以创建构建服务的层:

trait ServiceA {
  def doSomething(question: String): Task[String]
}

object ServiceA {
  val layer: ZLayer[HttpClient, Nothing, ServiceA] = ZLayer {
    for {
      httpClient <- ZIO.service[HttpClient]
      username <- ...get from config?
      password <- ...get from config?
      cache <- Ref.make(Map.empty[String, Cookie])
    } yield new RemoteServiceA(username, password, httpClient, cache)
  }
}

class RemoteServiceA(username: String, password: String, httpClient: HttpClient, cache: Ref[Map[String, Cookie]]) extends ServiceA {

  private val authorize: Task[Cookie] = ... as before ...

  override def doSomething(question: String): Task[String] = for {
    authCookie <- authorize.cachedBy(cache, username)  // <-- memoization
    req = HttpRequestBuilder().setUri(s"https://.../do-something?q=$question")
    answer <- httpClient.sendRequest(req)
  } yield answer


}
© www.soinside.com 2019 - 2024. All rights reserved.