Play-Slick: Можно ли улучшить этот дизайн (шаблон) ... и как его назвать?

Я использую Play-Slick версий 2.5.x и 3.1.x соответственно. Я использую генератор кода Slick и создаю модель Slick из существующей базы данных. На самом деле, я стесняюсь признать, что я руководствуюсь разработкой БД, а не разработкой классов.

Это начальная настройка:

  • Сгенерированная модель Slick под generated.Tables._
  • Общая реализация Slick Dao
  • Служебный слой, который строится поверх даос Generic Slick

Это силы, лежащие в основе шаблона, который я временно назвал "Pluggable Service", потому что он позволяет подключить функциональные возможности уровня сервиса к модели:

  • Контроллеры и представления Play должны видеть только слой Service (а не Dao), например UserService
  • Сгенерированная модель, например UserRow ожидается, что он будет соответствовать интерфейсам бизнес-уровня, например, субъекту Deadbolt-2, но не будет реализовывать его напрямую. Чтобы иметь возможность реализовать это, нужно "слишком много", например, UserRow тип модели, UserDao и потенциально некоторый бизнес-контекст.
  • Некоторые из UserService методы естественным образом применяются к модели UserRow пример, например loggedUser.roles или же loggedUser.changePassword

Поэтому у меня есть:

generated.Tables.scala Гладкая модель классов:

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

dao.UserDao.scala Расширения и настройки Dao, специфичные для модели User:

@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, который поможет избежать необходимости писать шаблонный код в объекте Pluggable Service? имеет ли смысл Pluggable Service?

1 ответ

Решение

Одним из распространенных вариантов может быть родительская черта для ваших контроллеров, которая имеет что-то вроде этого:

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 и что еще в один класс (например ControllerContext?) и внедрить этот единственный экземпляр как один параметр конструктора (но это, вероятно, другое обсуждение...).

После этого ваш код контроллера будет выглядеть так:

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

и то и другое user а также request неявный, который помогает перейти во внутренние части вашего приложения (что часто имеет место - вы приносите user объект для выполнения некоторой бизнес-логики).

Это не избавит вас от PluggableUserService per se (логика все еще там), но это может помочь вам легче повторно использовать одну и ту же логику везде в ваших контроллерах (как в моем опыте, вам нужно иметь оба User вместе с Roles чаще всего в реальном приложении).

РЕДАКТИРОВАТЬ: У меня было ощущение, что я не совсем понял ваш вопрос. Вы хотите избежать шаблон в PluggableUserService или вы хотите избежать рассеивания этого преобразования с использованием PluggableUserService везде, в каждом контроллере (ИМХО 2-й вариант - это то, чего следует избегать)?

Другие вопросы по тегам