Skip to content

Instantly share code, notes, and snippets.

@horothesun
Last active June 16, 2022 21:04
Show Gist options
  • Save horothesun/1036fb618a6f135a58c501aa94338e8d to your computer and use it in GitHub Desktop.
Save horothesun/1036fb618a6f135a58c501aa94338e8d to your computer and use it in GitHub Desktop.
Cats-effect - Cached client
import cats.effect.{IO, Resource}
case class Config()
case class InputA()
case class InputB()
case class InputC()
case class OutputA()
case class OutputB()
case class OutputC()
trait Client {
def getDataA(input: InputA): IO[OutputA]
def getDataB(input: InputB): IO[OutputB]
def getDataC(input: InputC): IO[OutputC]
}
/*
Default instance: Client.createDefault(config)
Cached instance: Client.cached(Client.createDefault(config))
*/
object Client {
def createDefault(config: Config): Resource[IO, Client] = ???
def cached(base: Resource[IO, Client]): Resource[IO, Client] =
base.evalMap(baseClient =>
(
IO.ref[Map[InputA, OutputA]](Map.empty),
IO.ref[Map[InputB, OutputB]](Map.empty),
IO.ref[Map[InputC, OutputC]](Map.empty)
).tupled
.map { case (outputACache, outputBCache, outputCCache) =>
new Client {
override def getDataA(input: InputA): IO[OutputA] =
cached(baseClient.getDataA, input, outputACache)
override def getDataB(input: InputB): IO[OutputB] =
cached(baseClient.getDataB, input, outputBCache)
override def getDataC(input: InputC): IO[OutputC] =
cached(baseClient.getDataC, input, outputCCache)
}
}
)
private def cached[Input, Output](
methodToCache: Input => IO[Output],
input: Input,
cacheRef: Ref[IO, Map[Input, Output]]
): IO[Output] =
cacheRef.get
.flatMap { cache =>
val ifCacheIsEmpty =
methodToCache(input)
.flatMap(output =>
cacheRef
.update(oldCache => oldCache + (input -> output))
.as(output)
)
cache.get(input).fold(ifCacheIsEmpty)(IO.pure)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment