zio and refilling a cache

zio and refilling a cache

In some small web app server projects I sometimes need to cache some data to reduce latency and the impact of queries on a “weak” backend database. We can use alot of clever caching tools but sometimes I just need something quick and dirty.

If you are using ZIO-style services, you may want the cache to be independent of other services and maintain the cache internally to the service environment. There are many ways to cache remote data, for example, you could also use ZQuery or the newer ZCache.

Let’s assume that we want to do something simple:

case class PeopleCache(
	byId: Map[UUID, Person] = Map(),
	last: LocalDateTime = LocalDateTime.now()
)

object PeopleCache:
  def createFromList(people: List[Person]) = ???

trait Service:
  def search(qstring: String): IO[Exception, Option[Person]

val live = ???

class PeopleServiceImpl(
	cache: RefM[PeopleCache], 
	db: db.Service) extends Service:
  def search(qstring: String) = cache.get.map(searchWithString(qstring, _))

We need to create a live service and update the cache every 30 minutes. It is quite simple using zio since we can compose effects into the layer definition. Once the layer is created and fed to our effectful program, the updating starts.

val live = 
  (for
    db <- ZIO.service[DB]
    cache <- ZRefM.make[PeopleCache](PeopleCache())
    // update cache and update ref
    _ <- cache
		    .update(old => refill(old.last, db))
            .repeat(zioScheduleEvery30Minutes)
            .forkDaemon
    _ <- 
  yield PeopleServiceImpl(cache, db): Service)
  .toLayer

// this replaces the cache, but you could just update
def refill(lastFill: LocalDateTime, db: db.Service) = 
  val paramBasedOnLastFill = ???
  for 
    newCache <- fetchPeople(paramBasedOnLastFill)
				    .map(PeopleCache.createFromList(_))
  yield newCache

Now our service will use whatever is in the ZRef. We use a ZRefM so that we can run the refill effect during the update operation. The update operation gives us the old cache so we know when it was last filled and we create and fill a new cache. We use forkDaemon and fork the fiber as a “daemon” fiber so that the fiber attaches to the parent. This allows us the program to cancel the refresh when the upper levels of the program terminate.

That’s it!

Comments

Popular posts from this blog

quick note on scala.js, react hooks, monix, auth

zio environment and modules pattern: zio, scala.js, react, query management

user experience, scala.js, cats-effect, IO