11

I'm developing a Play! 2.2 application in Scala with Slick 2.0 and I'm now tackling the data access aspect, trying to use the Cake Pattern. It seems promising but I really feel like I need to write a huge bunch of classes/traits/objects just to achieve something really simple. So I could use some light on this.

Taking a very simple example with a User concept, the way I understand it is we should have:

case class User(...) //model

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

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

So far it's totally reasonable. Now we add a "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
    }
  }
}

Then we have a UserService:

trait UserService {
 this: UserRepository =>
val userService: UserService
 class UserServiceImpl { //
    def findByName(name: String) = {
       userRepo.findByName(name)
    }
  }
}

Now we mix all of this in an object :

object UserModule extends UserService with UserRepository {
    val userRepo = new UserRepositoryImpl
    val userService = new UserServiceImpl 
}
  1. Is UserRepository really useful? I could write findByName as a custom query in Users slick object.

  2. Let's say I have another set of classes like this for Customer, and I need to use some UserService features in it.

Should I do:

CustomerService {
this: UserService =>
...
}

or

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

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

于 2014-04-04T20:33:27.963 回答
4

查看我最近发布的Slick 架构备忘单。它没有对数据库驱动程序进行抽象,但是以这种方式更改它是微不足道的。把它包起来

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

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

如果每个数据库表的重复语法开销困扰您,请生成代码。Slick 代码生成器正是为此目的而定制的:

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

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

于 2014-04-06T10:47:30.827 回答