-- Donald Knuth
  trait Service[Request, Response] {
    def apply(request: Request): Future[Response]
  }
  object Service {
    def apply[Request, Response](body: Request => Future[Response]) =
      new Service[Request, Response] {
        override def apply(request: Request) = body(request)
      }
  }
            
def monitored[Request, Response](name: String)
                                (service: Service[Request, Response]) =
  new Service[Request, Response] {
    override def apply(request: Request) = {
      NewRelic.incrementCounter(name)
      val start = System.currentTimeMillis
      val response = service(request)
      response onComplete { case _ =>
        val time = System.currentTimeMillis - start
        NewRelic.recordResponseTimeMetric(name, time)
      }
      response onError { case ex => NewRelic.noticeError(ex) }
      response
    }
  }
            
val getUsersBillings: Service[UsersBillingsReq, UsersBillingsRes] =
  monitored("UserServices.getUserBillings") {
    Service { request =>
      Future {
        val formattedBillings = for {
          user     <- userRepository.fetchUsers(request.userIds)
          contract <- user.contracts
          billings <- contract.billings
        } yield formatBilling(user, contract, billing)
        UsersBillingsRes(formattedBillings)
      }
    }
  }
            
// avg. user has 3 contacts
// avg. contract has 2 billings
val formattedBillings = for {
  user     <- userRepository.fetchUsers(request.userIds)
              // 1 DB query
  contract <- user.contracts
              // next 3 DB queries on avg.
  billing  <- contracts.billings
              // next 3*2=6 DB queries on avg.
} yield formatBilling(user, contract, billing)
        // total 10 queries on avg.
            
// avg. user has 3 contacts
// avg. contract has 2 billings
val users     = userRepository.fetchUsers(request.userIds)
                  .map(user => (user.id, user)).toMap
val contracts = contractRepository.findForUserIds(users.keys)
                  .map(contract => (contract.id, contract)).toMap
val billings  = billingRepository.findByContractIds(contracts.keys)
                // 3 queries in total
val formattedBillings = for {
  billing  <- billings
  contract <- contract.get(billing.contractId).toSeq
  user     <- users.get(contract.userId).toSeq
} yield formatBilling(user, contract, billing)
            
Future {
  val users     = userRepository.fetchUsers(request.userIds)
                    .map(user => (user.id, user)).toMap
  val contracts = contractRepository.findForUserIds(users.keys)
                    .map(contract => (contract.id, contract)).toMap
  val billings  = billingRepository.findByContractIds(contracts.keys)
  val formattedBillings = for {
    billing  <- billings
    contract <- contract.get(billing.contractId).toSeq
    user     <- users.get(contract.userId).toSeq
  } yield formatBilling(user, contract, billing)
  UsersBillingsRes(formattedBillings)
}
            
{
  "EntityKey(user, 1)" : {
    "user-billings-1,2,5": "",
    ...
  },
  "EntityKey(user, 2)" : {
    "user-billings-1,2,5": "",
    ...
  },
  ...
  "user-billings-1,2,5": [serialized value],
  ...
}
            
case class EntityKey(type: String, id: String)
trait CacheContext[T] {
  def entityKeys(value: T): Seq[EntityKey] // entities result depends on
  def serializer: Serializer[T]            // T -> String
  def deserializer: Deserializer[T]        // String -> T
}
            
trait CacheHandler {
  def getOrPut[T](valueKey: String, ttl: Duration)
                 (valueF => Future[T]))
                 (implicit cacheContext: CacheContext[T],
                           executionContext: ExecutionContext): Future[T]
  def invalidate(entityKeys: EntityKey*)
}
            
def get[T](key: String)
          (implicit cacheContext: CacheContext[T],
                    executionContext: ExecutionContext): Future[T] =
  redisClient.mget(key) map { gets =>
    gets.headOption map (_.toArray) map cacheContext.deserializer
  }
            
def put[T](valueKey: String, value: T, ttl: Duration)
          (implicit cacheContext: CacheContext[T],
                    executionContext: ExecutionContext): Future[Unit] = {
  val entityKeys = cacheContext.entityKeys(value)
  val bytes = cacheContext.serializer(value).bytes
  val transaction = redisClient.multi()
  transaction.setex(valueKey, ttl.toSeconds, bytes)
  entityKeys map (_.toString) map { entityKey =>
    transaction.hmset(entityKey, Map(valueKey -> Array[Bytes]()))
    transaction.expire(entityKey, maxTtl.toSeconds)
  }
  transaction.exec() map (())
}
            
def getOrPut[T](valueKey: String, ttl: Duration)
               (valueF: => Future[T]))
               (implicit cacheContext: CacheContext[T],
                         executionContext: ExecutionContext): Future[T] =
  get[T](valueKey) flatMap { optionValue =>
    optionValue map Future.successful getOrElse {
      for {
        value <- valueF
        _     <- put[T](valueKey, value, ttl)
      } yield value
    }
  }
            
val transaction = redisClient.multi()
val keys = entityKeys map (_.toString) map { key =>
  key -> transaction.hgetall(key) }
for {
  _ <- transaction.exec()
  invTransaction = redisClient.multi()
  allInvalidatedKeys <- Future.sequence(keys map { case (key, keyMapF) =>
    keyMapF map { keyMap =>
      val invKeys = keyMap.keys
      invTransaction.hdel(key, invKeys:_*)
      invKeys
    }
  }) map (_.flatten)
  _ = invTransaction.del(allInvalidatedKeys:_*)
  _ <- invTransaction.exec()
} yield ()
            
implicit val userBillingsContext = new CacheContext[UsersBillingsRes] {
  def entityKeys(value: UsersBillingsRes): Seq[EntityKey] = ...
  def serializer: Serializer[UsersBillingsRes]            = ...
  def deserializer: Deserializer[UsersBillingsRes]        = ...
}
            
cacheHandler.getOrPut[UsersBillingsRes](
    s"user-billings-${request.userIds.mkString}", 10 minutes) {
  Future {
    // ...
    UsersBillingsRes(formattedBillings)
  }
}
            
trait UsersControllerImpl extends UsersController {
  def getBillingsForCurrentUserAndContractor(userId: Long) =
      authenticatedRequest { request =>
    val observedEntityId   = userId
    val observedEntityType = "user"
    val currentUserId = currentUser.id
    for {
      result <- userServices.getContractBillingsForPair(
          ContractBillingsForPairReuqest(userId, currentUserId))
    } yield {
      // create JSON from user
    }
  }
}
            
def getOrPut[T](valueKey: String, ttl: Duration, request: Request)
               (valueF => Future[T]))
               (implicit cacheContext: RequestCacheContext[T],
                         executionContext: ExecutionContext): Future[T] = {
  val uri    = request.uri
  val header = request.headers.get(Headers.Authentication)
                              .getOrElse("")
  val params = request.params.map { case (name, value) =>
    name + ":" + value.sorted.toString
  }.toSeq.sorted
  val requestSuffix = s"$uri-$header-${params.mkString}"
  getOrPut[T](s"$value-$requestSuffix", ttl)(valueF)
}
            
def getBillingsForCurrentUserAndContractor(userId: Long) =
    authenticatedRequest { request =>
  val observedEntityId = userId
  val currentUserId    = currentUser.id
  cacheHandler.getOrPut[BillingJsonResult](
      "api-billings-for-$userId", 10 minutes, request) {
    for {
      result <- userServices.getContractBillingsForPair(
          ContractBillingsForPairReuqest(userId, currentUserId))
    } yield {
      // create JSON from user
    }
  }
}
            
val users     = userRepository.fetchUsers(request.userIds)
                  .map(user => (user.id, user)).toMap
val contracts = contractRepository.findForUserIds(users.keys)
                  .map(contract => (contract.id, contract)).toMap
val billings  = billingRepository.findByContractIds(contracts.keys)
val formattedBillings = for {
  billing  <- billings
  contract <- contract.get(billing.contractId).toSeq
  user     <- users.get(contract.userId).toSeq
} yield formatBilling(user, contract, billing)
UsersBillingsRes(formattedBillings)
            
[
  {
    "id": 5543,
    "date": "2015-10-23",
    ...
    "_links": ...,
    "_embedded": {
      "user": {
        "id": 1,
        "name": "John",
        "surname": "Smith",
        ...
      },
      "contract": {
        "id": 646,
        ...
      },
      "billings": {
        "id": 766,
        ...
      }
    }
  },
  ...
]
            No!
We actually needed:
// UserId -> Billings
val billingsByUsers: Map[Long, Seq[Billing]] =
    billingRepository.findByUserIds(request.userIds)
val formattedBillings = for {
  (userId, billings) <- billingsByUsers
  billing            <- billings
  contractId        = billing.contractId
} yield formatBilling(userId, contractId, billing)
UsersBillingsResponse(formattedBillings)
            
[
  {
    "id": 5543,
    "date": "2015-10-23",
    "userId": 1,
    "contractId": 646,
    ...
  },
  ...
]