2016-04-06 3 views
5

Я следовал принципу дизайна из книги Функциональное и реактивное моделирование.Как реализовать кеширование с помощью 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

Просто мысль ... мне кажется, что что-то вроде кэширования действительно не является поведением домена (то есть, что-то, что должно быть частью вашей службы), но, возможно, скорее свойство реализации Репозитория. Тогда ваша служба будет содержать только действия, необходимые для выполнения требуемого поведения, но (если вы захотите), ваше приложение может выбирать между кэшированием и не кэшированием репозиториев. В рамках реализации репозитория вы можете использовать нечто вроде государственной монады для более функционального подхода к кешированию. –

ответ

1

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

Копайте немного глубже в своей проблеме дизайна, вам интересно посмотреть на как зависимость инъекции может быть сделано в Scala:

  1. инъекции Конструктор
  2. торт шаблон
  3. Считыватель монада

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

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

Смежные вопросы