Git Product home page Git Product logo

play-scalajs-seed's Introduction

Play 2.5 with Scala JS cross building

This repo is a seed project for using ScalaJS cross-building with Play 2.5. Database schemas are auto-generated with Slick, and Flyway is used for migrations.

The seed repos for this project were:

Refer to those for more information and examples.

Note: Some features have been commented out. Refer to the above to see how to enable the database integration for example.

Development notes

Read below for more info on how this project is laid out. However, here are a few notes for development:

  • The slickCodegen sbt task sometimes says it's written output to Tables.scala but it hasn't. In that case, running sbt "project play" compile seems to fix the issue.
  • Once Tables.scala has been generated, edit the project structure in intellij and stop the slick/target directory from being excluded. Intellij needs to treat Tables.scala as a source file to make code that uses it load correctly.

This project is configured to keep all the modules self-contained.

  • Slick is isolated from Play, not using play-slick.
  • Database migration is done using Flyway, not Play Evolutions.
  • Slick's classes are auto-generated following database migration.

Database

The application is configured to use PostgreSQL and has some custom drivers to make Postgres / Slick integration easier.

If you are using PostgreSQL for the first time, follow the instructions to install on Mac using HomeBrew, and then start up PostgreSQL.

pg_ctl -D /usr/local/var/postgres start
sudo su - postgres # if on Linux

Then:

createdb myapp
createuser --pwprompt myuser

Database Migration

The first thing to do is to run the database scripts. Flyways has a number of advantages over Play Evolutions: it allows for both Java migrations and SQL migrations, and has command line support.

Start up sbt and go into the flyway module to run database migrations:

project flyway
flywayMigrate

See the flyways documentation for more details.

Slick

The Postgres Driver for Slick is configured with slick-pg, which allows for custom mapping between PostgreSQL data types and Joda Time data types:

trait MyPostgresDriver extends ExPostgresDriver
with PgArraySupport
with PgDateSupportJoda
with PgPlayJsonSupport {

  object MyAPI extends API with DateTimeImplicits with JsonImplicits {
    implicit val strListTypeMapper = new SimpleArrayJdbcType[String]("text").to(_.toList)
    implicit val playJsonArrayTypeMapper =
      new AdvancedArrayJdbcType[JsValue](pgjson,
        (s) => utils.SimpleArrayUtils.fromString[JsValue](Json.parse(_))(s).orNull,
        (v) => utils.SimpleArrayUtils.mkString[JsValue](_.toString())(v)
      ).to(_.toList)
  }

  // jsonb support is in postgres 9.4.0 onward; for 9.3.x use "json"
  def pgjson = "jsonb"

  override val api = MyAPI
}

object MyPostgresDriver extends MyPostgresDriver

Slick configuration is simple, because the Slick schema code generation will look at the tables created from Flyway, and automatically generate a Tables trait. From there, UsersRow and Users are created automatically. Some conversion code is necessary to map between UsersRow and User.

@Singleton
class SlickUserRepo @Inject()(db: Database) extends UserRepo with Tables {

  // Use the custom postgresql driver.
  override val profile: JdbcProfile = MyPostgresDriver

  import profile.api._

  private val queryById = Compiled(
    (id: Rep[Int]) => Users.filter(_.id === id))

  def lookup(id: Int)(implicit ec: UserRepoExecutionContext): Future[Option[User]] = {
    val f: Future[Option[UsersRow]] = db.run(queryById(id).result.headOption)
    f.map(maybeRow => maybeRow.map(usersRowToUser(_)))
  }

  def all(implicit ec: UserRepoExecutionContext): Future[Seq[User]] = {
    val f = db.run(Users.result)
    f.map(seq => seq.map(usersRowToUser(_)))
  }

  def update(user: User)(implicit ec: UserRepoExecutionContext): Future[Int] = {
    db.run(queryById(user.id).update(userToUsersRow(user)))
  }

  def delete(id: Int)(implicit ec: UserRepoExecutionContext): Future[Int] = {
    db.run(queryById(id).delete)
  }

  def create(user: User)(implicit ec: UserRepoExecutionContext): Future[Int] = {
    db.run(
      Users += userToUsersRow(user.copy(createdAt = DateTime.now()))
    )
  }

  def close(): Future[Unit] = {
    Future.successful(db.close())
  }

  private def userToUsersRow(user:User): UsersRow = {
    UsersRow(user.id, user.email, user.createdAt, user.updatedAt)
  }

  private def usersRowToUser(usersRow:UsersRow): User = {
    User(usersRow.id, usersRow.email, usersRow.createdAt, usersRow.updatedAt)
  }
}

Once SlickUserRepo is compiled, everything is available to be bound and run in the Play application.

Play

The root Module.scala file contains all the classes need to bind Slick and expose it as a UserRepo:

class Module(environment: Environment,
             configuration: Configuration) extends AbstractModule {
  override def configure(): Unit = {

    bind(classOf[Config]).toInstance(configuration.underlying)
    bind(classOf[UserRepoExecutionContext]).toProvider(classOf[SlickUserRepoExecutionContextProvider])

    bind(classOf[slick.jdbc.JdbcBackend.Database]).toProvider(classOf[DatabaseProvider])
    bind(classOf[UserRepo]).to(classOf[SlickUserRepo])

    bind(classOf[UserRepoCloseHook]).asEagerSingleton()
  }
}

There are a couple of providers to do a "lazy get" of the database and execution context from configuration:

@Singleton
class DatabaseProvider @Inject() (config: Config) extends Provider[slick.jdbc.JdbcBackend.Database] {

  private val db = slick.jdbc.JdbcBackend.Database.forConfig("myapp.database", config)

  override def get(): slick.jdbc.JdbcBackend.Database = db
}

@Singleton
class SlickUserRepoExecutionContextProvider @Inject() (actorSystem: akka.actor.ActorSystem) extends Provider[UserRepoExecutionContext] {
  private val instance = {
    val ec = actorSystem.dispatchers.lookup("myapp.database-dispatcher")
    new SlickUserRepoExecutionContext(ec)
  }

  override def get() = instance
}

class SlickUserRepoExecutionContext(ec: ExecutionContext) extends UserRepoExecutionContext {
  override def execute(runnable: Runnable): Unit = ec.execute(runnable)

  override def reportFailure(cause: Throwable): Unit = ec.reportFailure(cause)
}

The Repo must be closed to release JDBC connections, and this is handled through UserRepoCloseHook:

class UserRepoCloseHook @Inject()(repo: UserRepo, lifecycle: ApplicationLifecycle) {
  private val logger = org.slf4j.LoggerFactory.getLogger("application")

  lifecycle.addStopHook { () =>
    Future.successful {
      logger.info("Now closing database connections!")
      repo.close()
    }
  }
}

From there, the controller code is simple:

@Singleton
class HomeController @Inject() (userRepo: UserRepo, userRepoExecutionContext: UserRepoExecutionContext) extends Controller {

  private val logger = org.slf4j.LoggerFactory.getLogger(this.getClass)

  implicit val ec = userRepoExecutionContext

  def index = Action.async {
    logger.info("Calling index")
    userRepo.all.map { users =>
      logger.info(s"Calling index: users = ${users}")
      Ok(views.html.index(users))
    }
  }
}

Running

To run the project, start up Play:

project play
run

And that's it!

Now go to http://localhost:9000, and you will see the list of users in the database.

play-scalajs-seed's People

Stargazers

Serhii Polokhalo avatar

Watchers

 avatar James Cloos avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.