2016-04-06 16 views
5

Ho seguito il principio di progettazione dal libro Modellazione funzionale e reattiva.Come implementare la memorizzazione nella cache con Kleisli

Quindi tutti i metodi restituiscono Kleisli.

La domanda è come è possibile aggiungere una cache aggiornabile su questi servizi.

Ecco la mia attuale implementazione, c'è un modo migliore (combinatori esistenti, approccio più funzionale, ...)?

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] Per quanto riguarda il commento di @timotyperigo, ho attuazione del caching a livello di repository

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

Sono ancora interessato per il feedback per migliorare il design.

+1

Solo un pensiero ... mi sembra che qualcosa come il caching in realtà non sia un comportamento di dominio (cioè qualcosa che dovrebbe essere parte del tuo servizio), ma forse piuttosto una proprietà dell'implementazione del repository. Il tuo servizio conterrà quindi solo le azioni necessarie per eseguire il comportamento richiesto, ma (se lo desideri) la tua applicazione potrebbe scegliere tra caching e repository non in cache. Nell'implementazione del repository, è possibile utilizzare qualcosa come la monade di stato per un approccio più funzionale alla memorizzazione nella cache. –

risposta

1

Timothy ha perfettamente ragione: il caching è una funzionalità di implementazione del repository (e non del servizio). Le caratteristiche/dettagli di implementazione non dovrebbero essere esposti nei contratti e, a questo punto, stai andando bene con il tuo design (non con la tua implementazione, però!)

Scavando un po 'più in profondità nel tuo problema di progettazione, è interessante da guardare come Dependency Injection può essere fatto in Scala:

  1. iniezione Constructor
  2. modello torta
  3. Reader monade

Il modello di torta e l'iniezione del costruttore hanno una somiglianza: le dipendenze sono legate al momento della creazione. Con la monade Reader (Kleisli solo fornisce un ulteriore livello su di esso) si ritardo vincolante che si traduce in più componibilità (a causa delle combinatori), più testabilità e flessibilità

In caso di decorare una esistente TipRepository da aggiungendo funzionalità di caching, i benefici di Kleisli probabilmente non sarebbero necessari e potrebbero persino rendere il codice più difficile da leggere. L'uso dell'iniezione del costruttore sembra appropriato, dal momento che è lo schema più semplice che ti permette di fare le cose "bene"

Problemi correlati