Scala Slick蛋糕模式:超过9000个类?

11

我正在使用Scala和Slick 2.0开发Play! 2.2应用程序,并且现在正在处理数据访问方面,尝试使用Cake Pattern。 看起来很有前途,但实际上我觉得我需要编写大量的类/特征/对象才能实现真正简单的东西。因此,我需要一些指导。

以一个非常简单的User概念为例,我的理解是我们应该有:

case class User(...) //model

class Users extends Table[User]... //Slick Table

object users extends TableQuery[Users] { //Slick Query
//custom queries
}

到目前为止,这是完全合理的。现在我们添加一个“Cake Patternable” UserRepository

trait UserRepository {
 val userRepo: UserRepository
 class UserRepositoryImpl {
    //Here I can do some stuff with slick
    def findByName(name: String) = {
       users.withFilter(_.name === name).list
    }
  }
}

接下来我们有一个UserService:

trait UserService {
 this: UserRepository =>
val userService: UserService
 class UserServiceImpl { //
    def findByName(name: String) = {
       userRepo.findByName(name)
    }
  }
}
现在我们将所有这些混合在一个对象中:
object UserModule extends UserService with UserRepository {
    val userRepo = new UserRepositoryImpl
    val userService = new UserServiceImpl 
}
  1. UserRepository真的有用吗?我可以在Users光滑对象中编写findByName作为自定义查询。

  2. 假设我有另一组像这样的类用于Customer,并且我需要在其中使用一些UserService功能。

我应该怎么做:

CustomerService {
this: UserService =>
...
}
或者
CustomerService {
val userService = UserModule.userService
...
}

你从哪里得到了9000? - Blankman
1
@Blankman http://knowyourmeme.com/memes/its-over-9000 - user1498572
2个回答

10

好的,这些听起来是不错的目标:

  • 对数据库库(slick等)进行抽象化
  • 使特征单元测试可行

你可以像这样做:

trait UserRepository {
    type User
    def findByName(name: String): User
}

// Implementation using Slick
trait SlickUserRepository extends UserRepository {
    case class User()
    def findByName(name: String) = {
        // Slick code
    }
}

// Implementation using Rough
trait RoughUserRepository extends UserRepository {
    case class User()
    def findByName(name: String) = {
        // Rough code
    }
}

那么对于CustomerRepository,您可以这样做:

trait CustomerRepository { this: UserRepository =>
}

trait SlickCustomerRepository extends CustomerRepository {
}

trait RoughCustomerRepository extends CustomerRepository {
}

根据您的后端喜好将它们组合起来:

object UserModuleWithSlick
    extends SlickUserRepository
    with SlickCustomerRepository

object UserModuleWithRough
    extends RoughUserRepository
    with RoughCustomerRepository

你可以这样创建可单元测试的对象:

object CustomerRepositoryTest extends CustomerRepository with UserRepository {
    type User = // some mock type
    def findByName(name: String) = {
        // some mock code
    }
}

你很正确地观察到了这两者之间的相似性。

trait CustomerRepository { this: UserRepository =>
}

object Module extends UserRepository with CustomerRepository

trait CustomerRepository {
    val userRepository: UserRepository
    import userRepository._
}

object UserModule extends UserRepository
object CustomerModule extends CustomerRepository {
    val userRepository: UserModule.type = UserModule
}

这是旧的继承/聚合权衡,在Scala世界中进行了更新。每种方法都有优缺点。使用混合trait,您将创建较少的具体对象,这可以更容易跟踪(例如上面,您只有一个单一的Module对象,而不是用户和客户的单独对象)。另一方面,trait必须在对象创建时混合,因此您无法例如采取现有的UserRepository并通过混合创建一个CustomerRepository -- 如果您需要这样做,则必须使用聚合。还要注意,聚合通常需要您指定类似于上面的单例类型(: UserModule.type),以便Scala接受路径相关类型是相同的。混合trait的另一个强大之处是它可以处理递归依赖关系 -- UserModuleCustomerModule都可以向彼此提供一些内容并要求一些内容。使用lazy val也可以实现聚合,但使用混合trait更加简洁。


谢谢您的回答,这可能已经更好了。我想要的一个抽象是能够在需要时尽可能少地更改,从slick切换到其他东西的能力。 此外,我希望能够轻松地测试所有这些内容,这也是我考虑这种模式的原因,它有助于模拟类。 - user1498572
非常感谢您提供的出色解释和示例,现在我理解得更清楚了。单例类型和递归依赖项也是我遇到的问题,所以这对我也有所帮助。使用混入特性方法让我困扰的是,由于所有混入的依赖项,模块很快就包含了一堆存储库/服务/等等,因此当您使用一个模块时,您最终会访问到所有这些“附属”类,尽管您不一定需要或想要它们。 - user1498572

4

请查看我最近发布的Slick架构备忘单。它没有对数据库驱动程序进行抽象,但是很容易以这种方式更改。只需将其包装在

class Profile(profile: JdbcProfile){
  import profile.simple._
  lazy val db = ...
  // <- cheat sheet code here
}

您不需要使用蛋糕模式(Cake Pattern)。只需将所有代码放在一个文件中,即可避免使用它。蛋糕模式允许您将代码拆分为不同的文件,但如果您想要付出语法开销的话。人们还使用蛋糕模式创建不同的配置,包括不同组合的服务,但我认为这与您无关。
如果每个数据库表重复的语法开销困扰您,请生成代码。Slick代码生成器可以根据需要进行定制
如果您想混合手写和生成的代码,请将手写代码输入到代码生成器中,或者使用一种方案,其中生成的代码继承自手写代码或反之亦然。
如需使用其他库替换Slick,请用查询替换DAO方法。

良好的模板,你可能是对的,对于这种特殊情况,蛋糕模式可能是过度设计,但这只是我为了简单起见选择的一个基本示例,实际上应用程序更大,需要一些模块化。我尝试了代码生成器,它很好,但在与Play evolutions等方面存在一些副作用问题,我知道有解决方案,但我还不想涉及。此外,我喜欢完全将业务模型类(case类User)与任何DAO依赖项分开,并将它们放在单独的文件中有所帮助。但我会用你的模板来处理slick部分。 - user1498572
是的,我们需要提供一个开箱即用的slick代码生成和play(或slick)演化的集成。在这方面已经有了一些工作:http://blog.papauschek.com/2013/12/play-framework-evolutions-slick-2-0-code-generator/。 - cvogt
是的,我已经看到了,它看起来很有前途。当它稳定下来并且应用程序变得太大时,我可能最终会使用它,但现在我拥有的模型类数量不是很大,可以手动管理,并且这有助于我真正理解自己在做什么 :) - user1498572

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接