Как написать независимое от базы данных приложение Play и выполнить первоначальную инициализацию базы данных?
Я использую Slick с Play Framework 2.1, и у меня есть некоторые проблемы.
Учитывая следующую сущность...
package models
import scala.slick.driver.PostgresDriver.simple._
case class Account(id: Option[Long], email: String, password: String)
object Accounts extends Table[Account]("account") {
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
def email = column[String]("email")
def password = column[String]("password")
def * = id.? ~ email ~ password <> (Account, Account.unapply _)
}
... Мне нужно импортировать пакет для конкретного драйвера базы данных, но я хочу использовать H2 для тестирования и PostgreSQL в производстве. Как мне поступить?
Я смог обойти это, переопределив настройки драйвера в моем модульном тесте:
package test
import org.specs2.mutable._
import play.api.test._
import play.api.test.Helpers._
import scala.slick.driver.H2Driver.simple._
import Database.threadLocalSession
import models.{Accounts, Account}
class AccountSpec extends Specification {
"An Account" should {
"be creatable" in {
Database.forURL("jdbc:h2:mem:test1", driver = "org.h2.Driver") withSession {
Accounts.ddl.create
Accounts.insert(Account(None, "user@gmail.com", "Password"))
val account = for (account <- Accounts) yield account
account.first.id.get mustEqual 1
}
}
}
}
Мне не нравится это решение, и мне интересно, существует ли элегантный способ написания кода, независимого от БД, поэтому используются два разных движка базы данных - один в тестировании, а другой в производстве?
Я также не хочу использовать эволюцию и предпочитаю, чтобы Slick создавал таблицы базы данных для меня:
import play.api.Application
import play.api.GlobalSettings
import play.api.Play.current
import play.api.db.DB
import scala.slick.driver.PostgresDriver.simple._
import Database.threadLocalSession
import models.Accounts
object Global extends GlobalSettings {
override def onStart(app: Application) {
lazy val database = Database.forDataSource(DB.getDataSource())
database withSession {
Accounts.ddl.create
}
}
}
В первый раз, когда я запускаю приложение, все работает нормально... потом, конечно, во второй раз, когда я запускаю приложение, оно падает, потому что таблицы уже существуют в базе данных PostgreSQL.
Тем не менее, мои последние два вопроса:
- Как я могу определить, существуют ли таблицы базы данных?
- Как я могу сделать
onStart
метод выше DB-агностик, так что я могу проверить свое приложение сFakeApplication
?
4 ответа
Вы можете найти пример того, как использовать шаблон торт / внедрение зависимостей для отделения драйвера Slick от слоя доступа к базе данных здесь: https://github.com/slick/slick-examples.
Как отделить драйвер Slick и протестировать приложение с FakeApplication
Несколько дней назад я написал библиотеку интеграции Slick для Play, которая переносит зависимость драйвера в application.conf проекта Play: https://github.com/danieldietrich/slick-integration.
С помощью этой библиотеки ваш пример будет реализован следующим образом:
1) Добавить зависимость к проекту /Build.scala
"net.danieldietrich" %% "slick-integration" % "1.0-SNAPSHOT"
Добавить репозиторий снимков
resolvers += "Daniel's Repository" at "http://danieldietrich.net/repository/snapshots"
Или локальный репозиторий, если локальная интеграция опубликована
resolvers += Resolver.mavenLocal
2) Добавьте драйвер Slick в файл conf / application.conf.
slick.default.driver=scala.slick.driver.H2Driver
3) Реализовать приложение /models/Account.scala
В случае гладкой интеграции предполагается, что вы используете первичные ключи типа Long, которые автоматически увеличиваются. Имя ПК - это "id". Реализация Table/Mapper имеет методы по умолчанию (delete, findAll, findById, insert, update). Ваши сущности должны реализовать 'withId', который необходим для метода 'insert'.
package models
import scala.slick.integration._
case class Account(id: Option[Long], email: String, password: String)
extends Entity[Account] {
// currently needed by Mapper.create to set the auto generated id
def withId(id: Long): Account = copy(id = Some(id))
}
// use cake pattern to 'inject' the Slick driver
trait AccountComponent extends _Component { self: Profile =>
import profile.simple._
object Accounts extends Mapper[Account]("account") {
// def id is defined in Mapper
def email = column[String]("email")
def password = column[String]("password")
def * = id.? ~ email ~ password <> (Account, Account.unapply _)
}
}
4) Реализовать приложение /models/DAL.scala
Это уровень доступа к данным (DAL), который используется контроллерами для доступа к базе данных. Транзакции обрабатываются реализацией Table/Mapper в соответствующем Компоненте.
package models
import scala.slick.integration.PlayProfile
import scala.slick.integration._DAL
import scala.slick.lifted.DDL
import play.api.Play.current
class DAL(dbName: String) extends _DAL with AccountComponent
/* with FooBarBazComponent */ with PlayProfile {
// trait Profile implementation
val profile = loadProfile(dbName)
def db = dbProvider(dbName)
// _DAL.ddl implementation
lazy val ddl: DDL = Accounts.ddl // ++ FooBarBazs.ddl
}
object DAL extends DAL("default")
5) Реализуем test/test/AccountSpec.scala
package test
import models._
import models.DAL._
import org.specs2.mutable.Specification
import play.api.test.FakeApplication
import play.api.test.Helpers._
import scala.slick.session.Session
class AccountSpec extends Specification {
def fakeApp[T](block: => T): T =
running(FakeApplication(additionalConfiguration = inMemoryDatabase() ++
Map("slick.default.driver" -> "scala.slick.driver.H2Driver",
"evolutionplugin" -> "disabled"))) {
try {
db.withSession { implicit s: Session =>
try {
create
block
} finally {
drop
}
}
}
}
"An Account" should {
"be creatable" in fakeApp {
val account = Accounts.insert(Account(None, "user@gmail.com", "Password"))
val id = account.id
id mustNotEqual None
Accounts.findById(id.get) mustEqual Some(account)
}
}
}
Как определить, существуют ли таблицы базы данных?
Я не могу дать вам достаточный ответ на этот вопрос...
... но, возможно, это не совсем то, что вы хотите сделать. Что если вы добавите атрибут в таблицу, скажем Account.active
? Если вы хотите сохранить данные, которые в настоящее время хранятся в ваших таблицах, тогда скрипт alter сделает эту работу. В настоящее время такой сценарий изменения должен быть написан от руки. DAL.ddl.createStatements
может быть использован для извлечения операторов создания. Они должны быть отсортированы, чтобы быть лучше сопоставимыми с предыдущими версиями. Затем diff (с предыдущей версией) используется для ручного создания сценария изменения. Здесь эволюции используются для изменения схемы БД.
Вот пример того, как генерировать (первую) эволюцию:
object EvolutionGenerator extends App {
import models.DAL
import play.api.test._
import play.api.test.Helpers._
running(FakeApplication(additionalConfiguration = inMemoryDatabase() ++
Map("slick.default.driver" -> "scala.slick.driver.PostgresDriver",
"evolutionplugin" -> "disabled"))) {
val evolution = (
"""|# --- !Ups
|""" + DAL.ddl.createStatements.mkString("\n", ";\n\n", ";\n") +
"""|
|# --- !Downs
|""" + DAL.ddl.dropStatements.mkString("\n", ";\n\n", ";\n")).stripMargin
println(evolution)
}
}
Я также пытался решить эту проблему: возможность переключения баз данных между тестированием и производством. Идея обернуть каждый объект таблицы в черту не была привлекательной.
Я не пытаюсь обсудить плюсы и минусы схемы торта здесь, но я нашел другое решение для тех, кто заинтересован.
По сути, создайте объект, подобный этому:
package mypackage
import scala.slick.driver.H2Driver
import scala.slick.driver.ExtendedProfile
import scala.slick.driver.PostgresDriver
object MovableDriver {
val simple = profile.simple
lazy val profile: ExtendedProfile = {
sys.env.get("database") match {
case Some("postgres") => PostgresDriver
case _ => H2Driver
}
}
}
Очевидно, что вы можете сделать любую логику решения, которая вам нравится, здесь. Он не должен основываться на системных свойствах.
Теперь вместо:
import scala.slick.driver.H2Driver.simple._
Ты можешь сказать
import mypackage.MovableDriver.simple._
ОБНОВЛЕНИЕ: версия Slick 3.0, любезно предоставленная trent-ahrens:
package mypackage
import com.typesafe.config.ConfigFactory
import scala.slick.driver.{H2Driver, JdbcDriver, MySQLDriver}
object AgnosticDriver {
val simple = profile.simple
lazy val profile: JdbcDriver = {
sys.env.get("DB_ENVIRONMENT") match {
case Some(e) => ConfigFactory.load().getString(s"$e.slickDriver") match {
case "scala.slick.driver.H2Driver" => H2Driver
case "scala.slick.driver.MySQLDriver" => MySQLDriver
}
case _ => H2Driver
}
}
}
Play-Slick делает то же самое, что предлагается в других ответах, и, похоже, находится под эгидой Play/Typesafe.
Вы просто можете импортировать import play.api.db.slick.Config.driver.simple._
и он выберет подходящий драйвер в соответствии с conf/application.conf
,
Он также предлагает еще несколько вещей, таких как пул соединений, генерация DDL...
Если, как и я, вы не используете Play! для проекта, решение предоставлено Nishruu здесь