Using Couchbase as cache over Slick

2019-09-06 19:20发布

I'm trying to use Couchbase as a cache layer for a relational database that is accessed using Slick. The skeleton of my code that's relevant to the question is as follows:

class RdbTable[T <: Table[_]](implicit val bucket: CouchbaseBucket) {
  type ElementType = T#TableElementType
  private val table = TableQuery[T].baseTableRow

  private def cacheAll(implicit session: Session) =
    TableQuery[T].list foreach (elem => cache(elem))

  private def cache(elem: ElementType) =
    table.primaryKeys foreach (pk => bucket.set[ElementType](key(pk, elem), elem))

  private def key(pk: PrimaryKey, elem: ElementType) = ???
  .......
}

As you can see, I want to cache each element by all of its primary keys. For this purpose, I need to obtain the value of that key for the given element. But I don't see an obvious way to compute the value of a primary key (the column value, if single-column key; the tuple value, if multi-column).

Any suggestions on what to do? Note that the code MUST NOT know what the actual tables and their columns are. It must be completely general.

2条回答
forever°为你锁心
2楼-- · 2019-09-06 19:59

Slick doesn't come with a built-in primary key extractor for entities. What you can do is use either interfaces, type classes or reflection. E.g. variants of the following:

Either make your entities implement a trait

trait HasPrimaryKey{
  def primaryKey: Any
}
class RdbTable[T <: Table[_ <: HasPrimaryKey]](implicit val bucket: CouchbaseBucket) {
  ...
  private def key(elem: ElementType) = elem.primaryKey

// and for each entity:
case class Person( ... ) extends HasPrimaryKey{
  def primaryKey = ...
}

or a type class

trait KeyTypeClass[E,T <: Table[E]]{
  def key(e: E): Any
}
class RdbTable[T <: Table[_]](implicit val bucket: CouchbaseBucket, keyTC: KeyTypeClass[T]) {
  ...
  private def key(elem: ElementType) = keyTC(elem)

// and for each entity:
implicit val personKey = new KeyTypeClass[Person,PersonTable]{
  def key(p: Person) = ...
}

or using reflection to iterate over the primary keys and pull the values out of corresponding fields of the entity.

Code generation as mentioned by Zim-Zam can help with the repetitive elements.

查看更多
我欲成王,谁敢阻挡
3楼-- · 2019-09-06 20:11

We're doing something similar, using Redis as the cache. Each of our records only has one primary key, but in some cases we need to include additional data with the cache key to avoid ambiguity (for example, we have a ManyToMany record that represents an association between two records; when we return a ManyToMany record we'll embed one (but not both) of the associated records, and so in the cache key we need to include the type of the associated record that we're returning).

abstract trait Record {
  val cacheKey: CacheKey
}

trait ManyToManyRecord extends Record {
  override val cacheKey: ManyToManyCacheKey
}

class CacheKey(recordType: String, key: Int) {
  def getKey: String = recordType + ":" + key.toString
}

class ManyToManyCacheKey(recordType: String, key: Int, assocType: String) extends CacheKey {
  def getKey: String = recordType + ":" + key.toString + ":" + assocType
}

All of our tables use an integer primary key called "id", so it's easy for us to figure out what the value of "key" is. If you're working with a more complicated schema and don't want to manually write out the "def key: String" (or whatever) definitions for all of your record / table types, then you could try using Slick code generation to automatically generate record / table classes / objects with "def key" created directly from the schema. However, the learning curve for Slick code generation (or any other code generation tool) is steep, so if this is your only use for it then you'd probably be better off generating "def key" by hand. (We generate somewhere between 20-30% of our code using the code generation tool, so the initial investment in learning how to use the tool has paid off)

查看更多
登录 后发表回答