我正在开发一个Play! 2.2使用Slick 2.0在Scala中的应用程序我现在正在处理数据访问方面,试图使用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
}
是 UserRepository
真有用吗?我可以写 findByName
作为自定义查询 Users
光滑的物体。
假设我有另一组这样的类 Customer
,我需要使用一些 UserService
其中的功能。
我应该这样做:
CustomerService {
this: UserService =>
...
}
要么
CustomerService {
val userService = UserModule.userService
...
}
好吧,那些听起来像是好目标:
你可以这样做:
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接受路径依赖类型是相同的。混合特征的另一个功能是它可以处理递归依赖 - 两者都有 UserModule
和 CustomerModule
可以提供一些东西,并要求彼此的东西。使用惰性val进行聚合也是可能的,但是在混合特性方面它在语法上更方便。
看看我最近发表的 光滑的架构备忘单。它没有抽象出数据库驱动程序,但是通过这种方式改变它是微不足道的。把它包起来
class Profile(profile: JdbcProfile){
import profile.simple._
lazy val db = ...
// <- cheat sheet code here
}
你不需要蛋糕模式。只需将其全部放在一个文件中即可离开。如果您愿意支付语法开销,则蛋糕模式允许您将代码拆分为不同的文件。人们还使用蛋糕模式来创建不同的配置,包括不同的服务组合,但我认为这与您无关。
如果每个数据库表的重复语法开销困扰您,请生成代码。该 光滑的代码生成器是可定制的正是为了这个目的:
如果要混合手写和生成的代码,请将手写代码提供给代码生成器或使用方案,其中生成的代码继承自手写的反之亦然。
要通过其他方式替换Slick,请使用其他库将查询替换为DAO方法。