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-й вариант - это то, чего следует избегать)?