I'm implementing a web service using Slick (3.3) for the DB layer. I am trying to make the Slick implementation as generic as possible, hoping to achieve DB-agnosticism as well as generic table, table query, and DAO classes that abstract over the services models as much as possible. I'm trying to do this combining several techniques:
- A model hierarchy extending from a common base trait
- A table hierarchy, extracting common columns for model traits (inspired by http://gavinschulz.com/posts/2016-01-30-common-model-fields-with-slick-3-part-i.html)
- DB-agnosticism, relying on the JdbcProfile trait rather than any DB-specific profile implementation ( as described here: https://stackoverflow.com/a/31128239/4234254 and in the slick multi-db docs)
- Cake pattern for dependency injection
I'm having trouble layering some of the schema elements, however, and not being a Scala type expert, I've been unable to figure out the solution on my own. I've created a reproduction of the issue, trying to minimalize it as much as possible, and using a mock slick library. The full code can be found here: https://github.com/anqit/slick_cake_minimal_error_repro/blob/master/src/main/scala/com/anqit/repro/Repro.scala but I'll go through it below.
My "slick" library and model classes:
abstract class Table[E]
type TableQuery[W <: Table[_]] = List[W] // not actually a list, but need a concrete type constructor to demonstrate the issue
object TableQuery {
def apply[W <: Table[_]]: TableQuery[W] = List[W]()
}
trait BaseEntity
case class SubEntityA() extends BaseEntity
case class SubEntityB() extends BaseEntity
Combining technique 2 in the list above with the cake pattern, I'm creating traits that wrap schema elements for each model. The base schema includes columns common to entity tables (e.g. id), and entity tables inherit from that:
trait BaseSchema[E <: BaseEntity] {
// provides common functionality
abstract class BaseTableImpl[E] extends Table[E]
def wrapper: TableQuery[_ <: BaseTableImpl[E]]
}
// functionality specific to SubEntityA
trait SchemaA extends BaseSchema[SubEntityA] {
class TableA extends BaseTableImpl[SubEntityA]
// this definition compiles fine without a type annotation
val queryA = TableQuery[TableA]
def wrapper = queryA
}
// functionality specific to SubEntityB that depends on SchemaA
trait SchemaB extends BaseSchema[SubEntityB] { self: SchemaA =>
class TableB extends BaseTableImpl[SubEntityB] {
// uses SchemaA's queryA to make a FK
}
/*
attempting to define wrapper here without a type annotation results in the following compilation error (unlike defining wrapper for SchemaA above):
def wrapper = Wrapper[WrappedB]
type mismatch;
[error] found : Repro.this.Wrapper[SubB.this.WrappedB]
[error] (which expands to) List[SubB.this.WrappedB]
[error] required: Repro.this.Wrapper[_ <: SubB.this.BaseWrapMeImpl[_1]]
[error] (which expands to) List[_ <: SubB.this.BaseWrapMeImpl[_1]]
[error] def wrapper = Wrapper[WrappedB]
[error] ^
it does, however, compile if defined with an explicit type annotation as below
*/
val queryB = TableQuery[TableB]
def wrapper: TableQuery[TableB] = queryB
}
This is where I get my first error, a type mismatch, that I have currently worked around using an explicit type annotation, but I suspect it is related to the main error, stay tuned.
A base DAO, that will provide common query methods:
trait BaseDao[E <: BaseEntity] { self: BaseSchema[E] => }
And finally, putting all the cake layers together:
// now, the actual injection of the traits
class DaoA extends SchemaA
with BaseDao[SubEntityA]
// so far so good...
class DaoB extends SchemaA
with SchemaB
with BaseDao[SubEntityB] // blargh! failure! :
/*
illegal inheritance;
[error] self-type Repro.this.DaoB does not conform to Repro.this.BaseDao[Repro.this.SubEntityB]'s selftype Repro.this.BaseDao[Repro.this.SubEntityB] with Repro.this.BaseSchema[Repro.this.SubEntityB]
[error] with BaseDao[SubEntityB]
[error] ^
*/
The first error (the type mismatch in SchemaB), I'm completely at a loss. One of the few tricks in my bag is to add explicit type annotations when I run in to type-related errors in Scala, which is the only reason I tried that, and got it to compile. I would love an explanation as to why that is happening, and I suspect fixing my code such that I can write that without the type would probably help me with the second error. Which brings me to... the second error. To me, it looks like I've included all of the necessary traits to satisfy the self-type tree, but I guess not. My guess is that SchemaB
, while extending BaseSchema[SubEntityB]
, is somehow not being recognized as a BaseSchema[SubEntityB]
? Have I not set up my hierarchy properly? Or maybe I need to use bounds instead of strict type references?
question from:
https://stackoverflow.com/questions/65546488/illegal-inheritance-implementing-cake-pattern-with-slick