【问题标题】:Scala Slick Cake Pattern: over 9000 classes?Scala Slick Cake Pattern:超过 9000 个类?
【发布时间】:2015-12-30 01:45:40
【问题描述】:

我正在开发一个 Play! 2.2 应用程序在 Scala 中使用 Slick 2.0,我现在正在处理数据访问方面,尝试使用 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 真的有用吗?我可以将findByName 写为Users 光滑对象中的自定义查询。

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

我应该这样做:

CustomerService {
this: UserService =>
...
}

CustomerService {
val userService = UserModule.userService
...
}

【问题讨论】:

标签: scala slick cake-pattern


【解决方案1】:

好的,这些听起来不错:

  • 对数据库库的抽象(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 世界进行了更新。每种方法都有优点和缺点。使用混合特征,您将创建更少的具体对象,这可以更容易跟踪(如上所述,您只有一个 Module 对象,而不是用户和客户的单独对象)。另一方面,特征必须在对象创建时混合,因此您不能例如获取现有的UserRepository 并通过混合来创建CustomerRepository - 如果您需要这样做,则必须使用聚合.另请注意,聚合通常需要您指定像上面这样的单例类型 (: UserModule.type),以便 Scala 接受路径相关类型是相同的。混合特征的另一个强大之处在于它可以处理递归依赖关系——UserModuleCustomerModule 可以相互提供一些东西,也可以相互要求一些东西。这也可以通过使用惰性 val 进行聚合,但使用混合特征在语法上更方便。

【讨论】:

  • 感谢您的回答,这可能已经更好了。我想要的一种抽象是能够在需要时从 slick 切换到其他东西,并且尽可能少地进行更改。此外,我希望能够轻松测试所有这些,这也是我考虑这种有助于模拟类的模式的原因。
  • 感谢您的精彩解释和示例,现在更清楚了。单例类型和递归依赖也是我遇到的问题,所以它也对此有所帮助。 mixin trait 方法困扰我的是,由于所有混合依赖项,模块很快包含一大堆存储库/服务/任何东西,所以当你使用一个模块时,你最终可以访问所有这些“抵押品”虽然你不一定需要/想要上课。
【解决方案2】:

查看我最近发布的Slick architecture cheat sheet。它不会对数据库驱动程序进行抽象,但是以这种方式更改它是微不足道的。把它包起来

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

您不需要蛋糕图案。只需将所有内容放在一个文件中,您就可以不使用它。如果您愿意支付语法开销,蛋糕模式允许您将代码拆分为不同的文件。人们还使用蛋糕模式来创建不同的配置,包括不同的服务组合,但我认为这与您无关。

如果每个数据库表的重复语法开销困扰您,请生成代码。 Slick code-generator is customizable 正是为此目的:

如果您想混合手写代码和生成代码,要么将手写代码输入代码生成器,要么使用一种方案,其中生成的代码继承自手写代码,反之亦然。

要用其他东西替换 Slick,请将 DAO 方法替换为使用另一个库的查询。

【讨论】:

  • 好的模板,你可能是对的,对于这种特殊情况,蛋糕模式可能是一种矫枉过正,但这是我为简单起见而举的一个基本示例,应用程序实际上更大并且可以使用一些模块化。我尝试了代码生成器,它很好,但游戏演变存在一些副作用问题,例如,我知道有解决方案,但我还不想进入。此外,我喜欢将业务模型类(案例类用户)与任何 DAO 依赖项完全分开,并将它们放在单独的文件中会有所帮助。但我会用你的模板来做光滑的部分。
  • 是的,我们需要提供 slick 代码生成和播放(或 slick)演变的开箱即用集成。这里有这方面的工作:blog.papauschek.com/2013/12/…
  • 是的,我已经看到它看起来很有希望。当它稳定并且应用程序变得太大时,我可能最终会使用它,但现在我拥有的模型类的数量不是那么大,可以手动管理,而且它有助于我真正理解我在做什么: )
猜你喜欢
  • 1970-01-01
  • 2014-09-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多