Как написать приложение database-agnostic Play и выполнить первую инициализацию базы данных?


я использую пятно С 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
      }
    }
  }
}

мне не нравится это решение, и мне интересно, есть ли элегантный способ написания кода DB-agnostic, поэтому используются два разных ядра баз данных - один в тестировании, а другой в производстве?

Я также не хочу использовать evolution и предпочитаю, чтобы 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.

тем не менее, мои последние два вопроса:

  1. как я могу определить, существуют ли уже таблицы базы данных?
  2. как я могу сделать onStart метод выше DB-agnostic, так что я могу проверить мое приложение с FakeApplication?
4 62

4 ответа:

вы найдете пример того, как использовать Cake pattern / dependency injection, чтобы отделить драйвер Slick от уровня доступа к базе данных здесь:https://github.com/slick/slick-examples.

как отделить драйвер Slick и тестовое приложение с помощью FakeApplication

несколько дней назад я написал гладкую библиотеку интеграции для play, которая перемещает зависимость драйвера в приложение.конф проекта играть : https://github.com/danieldietrich/slick-integration.

С помощью этой библиотеки ваш пример будет осуществляться следующим образом:

1) добавьте зависимость к проекту / сборке.скала

"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) реализовать приложение / модели / аккаунт.скала

в случае Слик-интеграции предполагается, что вы используете первичные ключи типа Long, которые автоматически увеличиваются. ПК имя является 'идентификатор'. Таблица/реализации маппер имеет стандартные методы (удаление, метод findAll, findbyid осуществляет, вставка, обновление). Ваши сущности должны реализовать '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) Реализовать приложение / модели / DAL.скала

это уровень доступа к данным (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) реализовать тест/тест/AccountSpec.скала

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

}

как определить, существуют ли уже таблицы базы данных

Я не могу дать вам исчерпывающего ответа на этот вопрос...

... но, возможно, это не совсем так s.th ты хочешь это сделать. Что делать, если вы добавляете атрибут в таблицу, скажем Account.active? Если вы хотите сохранить данные, хранящиеся в настоящее время в ваших таблицах, то сценарий alter выполнит эту работу. В настоящее время такой сценарий alter должен быть написан вручную. Элемент DAL.ddl.createStatements может использоваться для получения инструкций create. Они должны быть отсортированы, чтобы быть лучше сопоставимы с предыдущими версиями. Затем diff (с предыдущей версией) используется вручную создать скрипт Alter. Здесь эволюции используются для изменения схемы БД.

вот пример того, как создать (первую) эволюцию:

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._

обновление: гладкая версия 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
    }
  }
}

The play-slick делает точно так же, как то, что предлагается в других ответах, и, похоже, находится под зонтиком Play/Typesafe.

вы просто можете импортировать import play.api.db.slick.Config.driver.simple._ и он выберет соответствующий драйвер в соответствии с conf/application.conf.

Он также предлагает еще несколько вещей, таких как пул соединений, генерация DDL...

Если, как и я, вы не используете Play! для проекта решение предоставляется Nishruu здесь