How to implement caching with Kleisli

I followed the design principle from the book Functional and Reactive Modeling.

So, all service methods return Kleisli .

The question is how can I add an updated cache for these services.

Here is my current implementation, is there a better way (existing combinators, a more functional approach, ...)?

 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)) } 

[update] Regarding the comment by @timotyperigo, I implement repository-level caching

 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 = … } 

I'm interested in feedback to improve the design.

+5
source share
1 answer

Timothy is absolutely right: caching is a function of implementing a repository (not a service). Functions / implementation details should not be disclosed in contracts, and at the moment you feel good in your design (but not with your implementation!)

Delving a little deeper into your design problem, you are interested to see how dependency injection can be done in Scala:

  • Designer Input
  • Cake drawing
  • Monad Reader

Cake drawing and constructor injection have one thing in common: dependencies are tied during creation. With the Reader monad (Kleisli just provides an extra layer on top of it) you delay the snapping, which leads to more layout ability (due to combinators), more testability and more flexibility.

If you decorate an existing TipRepository by adding caching functionality, Kleisli’s benefits are probably not needed, and they may even make the code more difficult to read. Using a constructor installation seems appropriate, as it is the simplest template that allows you to do something "well"

+1
source

Source: https://habr.com/ru/post/1246588/


All Articles