Development Tip

데이터베이스에 구애받지 않는 Play 애플리케이션을 작성하고 최초 데이터베이스 초기화를 수행하는 방법은 무엇입니까?

yourdevel 2020. 11. 28. 12:33
반응형

데이터베이스에 구애받지 않는 Play 애플리케이션을 작성하고 최초 데이터베이스 초기화를 수행하는 방법은 무엇입니까?


Play Framework 2.1과 함께 Slick사용 하고 있는데 몇 가지 문제가 있습니다.

다음 엔티티가 주어지면 ...

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에 구애받지 않는 코드를 작성하는 우아한 방법이 있는지 궁금합니다. 그래서 두 개의 다른 데이터베이스 엔진이 사용됩니다. 하나는 테스트 용이고 다른 하나는 프로덕션 용입니까?

나는 진화를 사용하고 싶지 않으며 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에 구애받지 않는 위 방법을 어떻게 만들 FakeApplication있습니까?

케이크 패턴 / 종속성 주입을 사용하여 데이터베이스 액세스 계층에서 Slick 드라이버를 분리하는 방법에 대한 예는 https://github.com/slick/slick-examples에서 찾을 수 있습니다.

Slick 드라이버를 분리하고 FakeApplication을 사용하여 애플리케이션을 테스트하는 방법

며칠 전 저는 Slick 통합 라이브러리를 작성하여 Play 프로젝트의 application.conf로 이동합니다 : https://github.com/danieldietrich/slick-integration .

이 라이브러리의 도움으로 예제는 다음과 같이 구현됩니다.

1) project / Build.scala에 종속성 추가

"net.danieldietrich" %% "slick-integration" % "1.0-SNAPSHOT"

스냅 샷 저장소 추가

resolvers += "Daniel's Repository" at "http://danieldietrich.net/repository/snapshots"

또는 매끄러운 통합이 로컬로 게시 된 경우 로컬 저장소

resolvers += Resolver.mavenLocal

2) conf / application.conf에 Slick 드라이버 추가

slick.default.driver=scala.slick.driver.H2Driver

3) app / models / Account.scala 구현

매끄러운 통합의 경우 자동 증분되는 Long 유형의 기본 키를 사용한다고 가정합니다. pk 이름은 'id'입니다. Table / Mapper 구현에는 기본 메서드 (delete, findAll, findById, insert, update)가 있습니다. 엔터티는 'insert'메소드에 필요한 'withId'를 구현해야합니다.

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) app / models / DAL.scala 구현

이것은 컨트롤러가 데이터베이스에 액세스하는 데 사용하는 데이터 액세스 계층 (DAL)입니다. 트랜잭션은 해당 컴포넌트 내의 테이블 / 매퍼 구현에 의해 처리됩니다.

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

}

데이터베이스 테이블이 이미 존재하는지 확인하는 방법

이 질문에 대한 충분한 답변을 드릴 수 없습니다 ...

... but perhaps this is not really s.th you want to do. What if you add an attribute to an table, say Account.active? If you want to safe the data currently stored within your tables, then an alter script would do the job. Currently, such an alter script has to be written by hand. The DAL.ddl.createStatements could be used to retrieve the create statements. They should be sorted to be better comparable with previous versions. Then a diff (with previous version) is used to manually create the alter script. Here, evolutions are used to alter the db schema.

Here's an example on how to generate (the first) evolution:

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)

  }

}

I was also trying to address this problem: the ability to switch databases between test and production. The idea of wrapping each table object in a trait was unappealing.

I am not trying to discuss the pros and cons of the cake pattern here, but I found another solution, for those who are interested.

Basically, make an object like this:

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

Obviously, you can do any decision logic you like here. It does not have to be based on system properties.

Now, instead of:

import scala.slick.driver.H2Driver.simple._

You can say

import mypackage.MovableDriver.simple._

UPDATE: A Slick 3.0 Version, courtesy of 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 does exactly the same as what is proposed in the other answers, and it seems to be under the umbrella of Play/Typesafe.

You just can import import play.api.db.slick.Config.driver.simple._ and it will choose the appropriate driver according to conf/application.conf.

It also offers some more things like connection pooling, DDL generation...


If, like me, you're not using Play! for the project, a solution is provided by Nishruu here

참고URL : https://stackoverflow.com/questions/13661339/how-to-write-database-agnostic-play-application-and-perform-first-time-database

반응형