Как реализовать кеширование с помощью Kleisli

Я следовал принципу дизайна из книги " Функциональное и реактивное моделирование".

Таким образом, все методы обслуживания возвращаются 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 = …
}

Я все еще заинтересован в обратной связи для улучшения дизайна.

1 ответ

Тимофей абсолютно прав: кэширование - это особенность реализации репозитория (а не сервиса). Особенности / детали реализации не должны быть представлены в контрактах, и на этом этапе вы преуспеваете со своим дизайном (хотя не со своей реализацией, хотя!)

Если немного углубиться в проблему проектирования, вам интересно посмотреть, как можно внедрить зависимости в Scala:

  1. Конструктор инъекций
  2. Торт
  3. Читатель монада

Шаблон пирога и внедрение в конструктор имеют одно сходство: зависимости связаны во время создания. С монадой Reader (Kleisli просто предоставляет дополнительный слой поверх нее) вы задерживаете связывание, что приводит к большей комбинируемости (благодаря комбинаторам), большей тестируемости и большей гибкости

В случае украшения существующего TipRepository добавив функции кэширования, преимущества Kleisli, вероятно, не понадобятся, и они могут даже затруднить чтение кода. Использование инъекции в конструктор представляется целесообразным, так как это самый простой шаблон, который позволяет вам делать "хорошо"

Другие вопросы по тегам