首页 > 解决方案 > 使用 Slick 实现蛋糕模式的“非法继承”

问题描述

我正在为 DB 层使用 Slick (3.3) 实现 Web 服务。我试图使 Slick 实现尽可能通用,希望能够实现与 DB 无关的以及尽可能抽象服务模型的通用表、表查询和 DAO 类。我正在尝试结合几种技术来做到这一点:

  1. 从公共基本特征扩展的模型层次结构
  2. 表层次结构,提取模型特征的常用列(灵感来自http://gavinschulz.com/posts/2016-01-30-common-model-fields-with-slick-3-part-i.html
  3. DB-agnosticism,依赖于 JdbcProfile 特征而不是任何特定于 DB 的配置文件实现(如此处所述:https ://stackoverflow.com/a/31128239/4234254 和 slick multi-db docs)
  4. 依赖注入的蛋糕模式

但是,我在对某些模式元素进行分层时遇到了麻烦,而且我不是 Scala 类型专家,我无法自己找出解决方案。我已经创建了这个问题的复制品,试图尽可能地最小化它,并使用一个模拟的光滑库。完整的代码可以在这里找到:https ://github.com/anqit/slick_cake_minimal_error_repro/blob/master/src/main/scala/com/anqit/repro/Repro.scala但我会在下面详细介绍。

我的“光滑”库和模型类:

    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

将上面列表中的技术 2 与蛋糕模式相结合,我正在为每个模型创建包装模式元素的特征。基本模式包括实体表共有的列(例如 id),实体表继承自:

    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
    }

这是我遇到的第一个错误,类型不匹配,我目前使用显式类型注释解决了这个问题,但我怀疑它与主要错误有关,敬请期待。

一个基本的 DAO,它将提供常见的查询方法:

    trait BaseDao[E <: BaseEntity] { self: BaseSchema[E] => }

最后,将所有蛋糕层放在一起:

    // 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]              ^
     */

第一个错误(SchemaB 中的类型不匹配),我完全不知所措。我的包中的几个技巧之一是当我在 Scala 中遇到与类型相关的错误时添加显式类型注释,这是我尝试这样做并让它编译的唯一原因。我很想解释为什么会发生这种情况,并且我怀疑修复我的代码以便我可以在没有类型的情况下编写它可能会帮助我解决第二个错误。这让我想到了……第二个错误。对我来说,看起来我已经包含了满足自我类型树的所有必要特征,但我想没有。我的猜测是SchemaB,在扩展的同时BaseSchema[SubEntityB],不知何故不被认可为BaseSchema[SubEntityB]? 我没有正确设置我的层次结构吗?或者也许我需要使用边界而不是严格的类型引用?

标签: scalagenericsslick

解决方案


推荐阅读