【问题标题】:How to implement caching with Kleisli如何使用 Kleisli 实现缓存
【发布时间】:2016-04-06 20:36:56
【问题描述】:

我遵循Functional and Reactive Modeling一书中的设计原则。

所以所有 service 方法都返回Kleisli

问题是如何在这些服务上添加可更新缓存

这是我目前的实现,有没有更好的方法(现有的组合器,更多功能的方法,......)?

import scala.concurrent.duration.Duration
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.{Await, Future}
import scalaz.Kleisli

trait Repository {
  def all : Future[Seq[String]]
  def replaceAll(l: Seq[String]) : Future[Unit]
}

trait Service {
  def all = Kleisli[Future, Repository, Seq[String]] { _.all }
  def replaceAll(l: Seq[String]) = Kleisli[Future, Repository, Unit] { _.replaceAll(l) }
}

trait CacheService extends Service {
  var cache : Seq[String] = Seq.empty[String]

  override def all = Kleisli[Future, Repository, Seq[String]] { repo: Repository =>
    if (cache.isEmpty) {
      val fcache = repo.all
      fcache.foreach(cache = _)
      fcache
    }
      else
      Future.successful(cache)
  }

  override def replaceAll(l: Seq[String]) = Kleisli[Future, Repository, Unit] { repo: Repository =>
    cache = l
    repo.replaceAll(l)
  }
}

object CacheTest extends App {
  val repo = new Repository {
    override def replaceAll(l: Seq[String]): Future[Unit] = Future.successful()
    override def all: Future[Seq[String]] = Future.successful(Seq("1","2","3"))
  }
  val service = new CacheService {}

  println(Await.result(service.all(repo), Duration.Inf))
  Await.result(service.replaceAll(List("a"))(repo), Duration.Inf)
  println(Await.result(service.all(repo), Duration.Inf))
}

[更新] 关于@timotyperigo 的评论,我已经在存储库级别实现了缓存

class CachedTipRepository(val self:TipRepository) extends TipRepository {
  var cache: Seq[Tip] = Seq.empty[Tip]

  override def all: Future[Seq[Tip]] = …

  override def replace(tips: String): Unit = …
}

我仍然对改进设计感兴趣。

【问题讨论】:

  • 只是一个想法......在我看来,像缓存这样的东西真的不是域行为(即,应该是您服务的一部分),而可能是存储库的属性执行。然后,您的服务将仅包含执行所需行为所需的操作,但是(如果您愿意)您的应用程序可以在缓存和非缓存存储库之间进行选择。在存储库实现中,您可以使用 State monad 之类的东西来获得更实用的缓存方法。

标签: scala functional-programming scalaz kleisli


【解决方案1】:

Timothy 完全正确:缓存是存储库(而不是服务)的实现特性。实现特性/细节不应在合同中公开,此时您的设计做得很好(但不是您的实现!)

深入挖掘您的设计问题,看看如何在 Scala 中完成依赖注入会很有趣:

  1. 构造函数注入
  2. 蛋糕图案
  3. 阅读器单子

蛋糕模式和构造函数注入有一个相似之处:依赖关系在创建时绑定。使用 Reader monad(Kleisli 只是在其之上提供了一个额外的层)您延迟绑定,这会导致更多的可组合性(由于组合器)、更多的可测试性和更多的灵活性

如果通过添加缓存功能来装饰现有的TipRepository,则可能不需要 Kleisli 的好处,它们甚至可能使代码更难阅读。使用构造函数注入似乎是合适的,因为它是让你“做好”事情的最简单的模式

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-15
    • 1970-01-01
    • 2012-10-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多