Play-Slick:是否可以改进这种设计(模式)……以及如何称呼它?
Play-Slick: Is it possible to improve this design (pattern) ... and how to call it?
我分别使用 Play-Slick 版本 2.5.x 和 3.1.x。我使用 Slick 的代码生成器并从现有数据库生成 Slick 模型。实际上,我很害羞地承认我是数据库设计驱动的,而不是 class 设计驱动的。
这是初始设置:
- 在
generated.Tables._
下生成的 Slick 模型
- 通用 Slick dao 实现
- 构建在 Generic Slick dao 之上的服务层
这些是我暂时称为 "Pluggable Service" 的模式背后的力量,因为它允许将服务层功能插入模型:
- Play 的控制器和视图必须只能看到服务层(而不是 Dao 的),例如
UserService
- 生成的模型,例如
UserRow
应符合业务层接口,例如Deadbolt-2 的 Subject 但不直接实现它。为了能够实现它,需要 "too much" 例如UserRow
模型类型,UserDao
和潜在的一些业务上下文。
- 某些
UserService
方法自然适用于模型 UserRow
实例,例如loggedUser.roles
或 loggedUser.changePassword
因此我有:
generated.Tables.scala
光滑模型 classes:
case class UserRow(id: Long, username: String, firstName: String,
lastName : String, ...) extends EntityAutoInc[Long, UserRow]
dao.UserDao.scala
特定于用户模型的 Dao 扩展和自定义:
@Singleton
class UserDao @Inject()(protected val dbConfigProvider: DatabaseConfigProvider)
extends GenericDaoAutoIncImpl[User, UserRow, Long] (dbConfigProvider, User) {
//------------------------------------------------------------------------
def roles(user: UserRow) : Future[Seq[Role]] = {
val action = (for {
role <- SecurityRole
userRole <- UserSecurityRole if role.id === userRole.securityRoleId
user <- User if userRole.userId === user.id
} yield role).result
db.run(action)
}
}
services.UserService.scala
将所有用户操作伪装到 Play 应用程序其余部分的服务:
@Singleton
class UserService @Inject()(auth : PlayAuthenticate, userDao: UserDao) {
// implicitly executes a DBIO and waits indefinitely for
// the Future to complete
import utils.DbExecutionUtils._
//------------------------------------------------------------------------
// Deadbolt-2 Subject implementation expects a List[Role] type
def roles(user: UserRow) : List[Role] = {
val roles = userDao.roles(user)
roles.toList
}
}
services.PluggableUserService.scala
最终是实际的 "Pluggable" 将服务实现动态附加到模型类型的模式:
trait PluggableUserService extends be.objectify.deadbolt.scala.models.Subject {
override def roles: List[Role]
}
object PluggableUserService {
implicit class toPluggable(user: UserRow)(implicit userService: UserService)
extends PluggableUserService {
//------------------------------------------------------------------------
override def roles: List[Role] = {
userService.roles(user)
}
}
终于可以在控制器中做:
@Singleton
class Application @Inject() (implicit
val messagesApi: MessagesApi,
session: Session,
deadbolt: DeadboltActions,
userService: UserService) extends Controller with I18nSupport {
import services.PluggableUserService._
def index = deadbolt.WithAuthRequest()() { implicit request =>
Future {
val user: UserRow = userService.findUserInSession(session)
// auto-magically plugs the service to the model
val roles = user.roles
// ...
Ok(views.html.index)
}
}
是否有任何 Scala 方法可以帮助您不必在可插入服务对象中编写样板代码?可插拔服务名称有意义吗?
其中一个常见的变体可能是您的控制器的父特征,它具有以下特征:
def MyAction[A](bodyParser: BodyParser[A] = parse.anyContent)
(block: (UserWithRoles) => (AuthenticatedRequest[A]) => Future[Result]): Action[A] = {
deadbolt.WithAuthRequest()(bodyParser) { request =>
val user: UserRow = userService.findUserInSession(session)
// this may be as you had it originally
// but I don't see a reason not to
// simply pull it explicitly from db or
// to have it in the session together with roles in the first place (as below UserWithRoles class)
val roles = user.roles
block(UserWithRoles(user, roles))(request)
}
这里房间里的大象是你如何获得 userService
实例。好吧,您需要在控制器构造函数中明确要求它(与使用 DeadboltActions
的方式相同)。或者,您可以将 DeadboltActions
、UserService
和其他内容捆绑到一个 class(例如 ControllerContext
?)中,并将这个单个实例作为一个构造函数参数注入(但这可能是另一个讨论。 ..).
之后你的控制器代码将是这样的:
def index = MyAction() { implicit user => implicit request =>
Future {
// ...
Ok(views.html.index)
}
}
user
和 request
都是隐式的,这有助于传递到应用程序的内部部分(通常是这种情况 - 你带 user
对象来执行一些业务逻辑).
它并没有摆脱你的 PluggableUserService
本身(逻辑仍然存在)但它可以帮助你更容易地在你的控制器中的任何地方重用相同的逻辑(根据我的经验,你需要User
和 Roles
在任何实际应用中都经常出现。
编辑:我觉得我不太明白你的问题。你想避免在 PluggableUserService
中使用样板文件,或者你想避免在每个控制器中到处使用 PluggableUserService
来分散这种转换(恕我直言,第二个选项是要避免的)?
我分别使用 Play-Slick 版本 2.5.x 和 3.1.x。我使用 Slick 的代码生成器并从现有数据库生成 Slick 模型。实际上,我很害羞地承认我是数据库设计驱动的,而不是 class 设计驱动的。
这是初始设置:
- 在
generated.Tables._
下生成的 Slick 模型
- 通用 Slick dao 实现
- 构建在 Generic Slick dao 之上的服务层
这些是我暂时称为 "Pluggable Service" 的模式背后的力量,因为它允许将服务层功能插入模型:
- Play 的控制器和视图必须只能看到服务层(而不是 Dao 的),例如
UserService
- 生成的模型,例如
UserRow
应符合业务层接口,例如Deadbolt-2 的 Subject 但不直接实现它。为了能够实现它,需要 "too much" 例如UserRow
模型类型,UserDao
和潜在的一些业务上下文。 - 某些
UserService
方法自然适用于模型UserRow
实例,例如loggedUser.roles
或loggedUser.changePassword
因此我有:
generated.Tables.scala
光滑模型 classes:
case class UserRow(id: Long, username: String, firstName: String,
lastName : String, ...) extends EntityAutoInc[Long, UserRow]
dao.UserDao.scala
特定于用户模型的 Dao 扩展和自定义:
@Singleton
class UserDao @Inject()(protected val dbConfigProvider: DatabaseConfigProvider)
extends GenericDaoAutoIncImpl[User, UserRow, Long] (dbConfigProvider, User) {
//------------------------------------------------------------------------
def roles(user: UserRow) : Future[Seq[Role]] = {
val action = (for {
role <- SecurityRole
userRole <- UserSecurityRole if role.id === userRole.securityRoleId
user <- User if userRole.userId === user.id
} yield role).result
db.run(action)
}
}
services.UserService.scala
将所有用户操作伪装到 Play 应用程序其余部分的服务:
@Singleton
class UserService @Inject()(auth : PlayAuthenticate, userDao: UserDao) {
// implicitly executes a DBIO and waits indefinitely for
// the Future to complete
import utils.DbExecutionUtils._
//------------------------------------------------------------------------
// Deadbolt-2 Subject implementation expects a List[Role] type
def roles(user: UserRow) : List[Role] = {
val roles = userDao.roles(user)
roles.toList
}
}
services.PluggableUserService.scala
最终是实际的 "Pluggable" 将服务实现动态附加到模型类型的模式:
trait PluggableUserService extends be.objectify.deadbolt.scala.models.Subject {
override def roles: List[Role]
}
object PluggableUserService {
implicit class toPluggable(user: UserRow)(implicit userService: UserService)
extends PluggableUserService {
//------------------------------------------------------------------------
override def roles: List[Role] = {
userService.roles(user)
}
}
终于可以在控制器中做:
@Singleton
class Application @Inject() (implicit
val messagesApi: MessagesApi,
session: Session,
deadbolt: DeadboltActions,
userService: UserService) extends Controller with I18nSupport {
import services.PluggableUserService._
def index = deadbolt.WithAuthRequest()() { implicit request =>
Future {
val user: UserRow = userService.findUserInSession(session)
// auto-magically plugs the service to the model
val roles = user.roles
// ...
Ok(views.html.index)
}
}
是否有任何 Scala 方法可以帮助您不必在可插入服务对象中编写样板代码?可插拔服务名称有意义吗?
其中一个常见的变体可能是您的控制器的父特征,它具有以下特征:
def MyAction[A](bodyParser: BodyParser[A] = parse.anyContent)
(block: (UserWithRoles) => (AuthenticatedRequest[A]) => Future[Result]): Action[A] = {
deadbolt.WithAuthRequest()(bodyParser) { request =>
val user: UserRow = userService.findUserInSession(session)
// this may be as you had it originally
// but I don't see a reason not to
// simply pull it explicitly from db or
// to have it in the session together with roles in the first place (as below UserWithRoles class)
val roles = user.roles
block(UserWithRoles(user, roles))(request)
}
这里房间里的大象是你如何获得 userService
实例。好吧,您需要在控制器构造函数中明确要求它(与使用 DeadboltActions
的方式相同)。或者,您可以将 DeadboltActions
、UserService
和其他内容捆绑到一个 class(例如 ControllerContext
?)中,并将这个单个实例作为一个构造函数参数注入(但这可能是另一个讨论。 ..).
之后你的控制器代码将是这样的:
def index = MyAction() { implicit user => implicit request =>
Future {
// ...
Ok(views.html.index)
}
}
user
和 request
都是隐式的,这有助于传递到应用程序的内部部分(通常是这种情况 - 你带 user
对象来执行一些业务逻辑).
它并没有摆脱你的 PluggableUserService
本身(逻辑仍然存在)但它可以帮助你更容易地在你的控制器中的任何地方重用相同的逻辑(根据我的经验,你需要User
和 Roles
在任何实际应用中都经常出现。
编辑:我觉得我不太明白你的问题。你想避免在 PluggableUserService
中使用样板文件,或者你想避免在每个控制器中到处使用 PluggableUserService
来分散这种转换(恕我直言,第二个选项是要避免的)?