Play-Slick:是否可以改进这种设计(图案)...以及如何调用? [英] Play-Slick: Is it possible to improve this design (pattern) ... and how to call it?

查看:210
本文介绍了Play-Slick:是否可以改进这种设计(图案)...以及如何调用?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我分别使用Play-Slick版本2.5.x和3.1.x。我使用Slick的代码生成器,并从现有的数据库中生成Slick模型。其实我很害羞,承认我是DB设计驱动而不是类设计驱动。



这是初始设置:




  • 生成的Slick模型在 generated.Tables ._

  • Generic Slick dao实现

  • 建立在Generic Slick dao之上的服务层



因为它允许将服务层功能插入到模型中,所以临时称为可插拔服务的模式背后的力量:




  • Play的控制器和视图只能看到服务层(而不是Dao的) UserService

  • 生成的模型例如 UserRow 预计将符合业务层接口,例如Deadbolt-2的主题,但不直接实现。为了能够实现它,需要太多,例如 UserRow 模型类型, UserDao 和潜在的一些业务环境。

  • 一些 UserService 方法自然适用于模型 UserRow instance eg loggedUser.roles loggedUser.changePassword



因此我有:



generated.Tables.scala Slick模型类:

  case class UserRow(id:Long,username:String,firstName:String,
lastName: String,...)扩展EntityAutoInc [Long,UserRow]

dao.UserDao.scala 特定于用户模型的道扩展和自定义:

  @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){
//隐式执行一个DBIO,并无限期地等待
//未来完成
import utils.DbExecutionUtils._
// ------------------------------------- -----------------------------------
// Deadbolt-2主题实现需要一个List [角色] type
def roles(user:UserRow):List [Role] = {
val roles = userDao.roles(user)
roles.toList
}
}

services.PluggableUserService.scala 终于实际的可插拔拍动态地将服务实现附加到模型类型上:

  trait PluggableUserService extends be.objectify.deadbolt.scala.models.Subject { 
覆盖def角色:列表[角色]
}

对象PluggableUserService {
隐式类toPluggable(user:UserRow)(隐式userService:UserService)
扩展PluggableUserService {
// ----------------------------------------- -------------------------------
override def roles:List [Role] = {
userService .roles(user)
}
}

最后可以在控制器:

  @Singleton 
class Application @Inject()(implicit
val messagesApi:MessagesApi,
会话:会话,
deadbolt:DeadboltActions,
userService:UserService)扩展控制器与I18nSupport {
import services.PluggableUserService._

def index = deadbolt.WithAuthRequest()(){implicit request =>
未来{
val用户:UserRow = userService.findUserInSession(会话)
//自动魔术地将服务插入模型
val roles = user.roles
// ...
Ok(views.html.index)
}
}

有没有Scala方式可以帮助不必在可插拔服务对象中编写样板代码?可插拔服务名称是否有意义?

解决方案

常见变体之一可能是您的控制器的父属特征,这些行:

  def MyAction [A](bodyParser:BodyParser [A] = parse.anyContent)
:(UserWithRoles)=>(AuthenticatedRequest [A])=> Future [Result]):Action [A] = {
deadbolt.WithAuthRequest()(bodyParser){request =>
val user:UserRow = userService.findUserInSession(session)
//这可能是你原来的
//但是我没有看到没有理由不
/ /直接从db或
//中明确地将其与角色一起放在会话中(如以下UserWithRoles类)
val roles = user.roles

块(UserWithRoles(用户,角色))(请求)
}

这里的房间是你如何获得 userService 实例。那么你需要在你的控制器构造函数中显式地要求它(与 DeadboltActions 一样)。或者,您可以将 DeadboltActions UserService 以及其他类别(例如 ControllerContext ?),并将这个单个实例注入一个构造函数参数(但这可能是另一个讨论...)。



之后,你的控制器代码就像这个:

  def index = MyAction(){implicit user =>隐式请求=> 
未来{
// ...
Ok(views.html.index)
}
}

用户请求进入你的应用程序的内部部分(通常情况下 - 你带来用户对象来执行一些业务逻辑)。



它不会摆脱您的 PluggableUserService 本身(逻辑仍然存在),但它可能会帮助您在您的控制器中随处可以更轻松地重复使用相同的逻辑(根据我的经验,您需要同时使用用户角色更常见于任何真正的应用程序)。



编辑:我有一种感觉,我没有得到你的问题。您想避免在 PluggableUserService 中的样板,或者您希望避免在每个控制器中使用 PluggableUserService 分散此转换(IMHO第二选项是要避免的)?


I'm using Play-Slick versions 2.5.x and 3.1.x respectively. I use Slick's code generator and produce the Slick model from an existing database. Actually I'm shy to admit that I'm DB-design driven and not class-design driven.

This is the initial setup:

  • Generated Slick model under generated.Tables._
  • Generic Slick dao implementation
  • Service layer that builds on top of the Generic Slick dao

These are the forces behind the pattern which I temporary called "Pluggable Service" because it allows plugging in the service layer functionality to the model:

  • Play's controllers and views must only see the Service layer (and not the Dao's) e.g. UserService
  • Generated model e.g. UserRow is expected to comply to business layer interfaces e.g. Deadbolt-2's Subject but not implement it directly. To be able to implement it one needs "too much" e.g. the UserRow model type, the UserDao and potentially some business context.
  • Some of the UserService methods naturally apply to the model UserRow instance e.g. loggedUser.roles or loggedUser.changePassword

Therefore I have:

generated.Tables.scala Slick model classes:

case class UserRow(id: Long, username: String, firstName: String, 
                   lastName : String, ...) extends EntityAutoInc[Long, UserRow]

dao.UserDao.scala Dao extensions and customizations specific to the User model:

@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 service that facades all user operations to the rest of the Play application:

@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 finally the actual "Pluggable" pattern that dynamically attaches service implementations to the model type:

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)
    }
}

Finally one can do in the controllers:

@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)
    }
  }                                

Is there any Scala way that could help not having to write the boilerplate code in the Pluggable Service object? does the Pluggable Service name makes sense?

解决方案

One of the common variant may be a parent trait for your controllers that has something along these lines:

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)
}  

The elephant in the room here is how you get userService instance. Well you would need to explicitly require it in your controller constructor (in the same way you do with DeadboltActions). Alternatively you may bundle DeadboltActions, UserService and what else into one class (e.g. ControllerContext?) and inject this single instance as one constructor parameter (but that's probably another discussion...).

After that your controller code would be like this:

def index = MyAction() { implicit user => implicit request =>
    Future {
      // ...
      Ok(views.html.index)
    }
  } 

both user and request is implicit which helps to pass into into inner parts of your application (which is often the case - you bring user object to perform some business logic).

It doesn't get rid of your PluggableUserService per se (logic is still there) but it may help you to easier reuse same logic everywhere in your controllers (as in my experience, you need to have both User together with Roles more often than not in any real application).

EDIT: I got a feeling I didn't quite get your question. You want to avoid boilerplate in PluggableUserService or you want to avoid scattering this conversion with use of PluggableUserService everywhere, in every controller (IMHO 2nd option is something to be avoided)?

这篇关于Play-Slick:是否可以改进这种设计(图案)...以及如何调用?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆