Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
Eishay Smith (CTO & Founder)
@eishay of @42eng
on @
42go.com
Agenda
• Intro to Slick
• How we use it at FortyTwo
• Wrapping Sessions
• Generic Access Layer
About Slick
Database query library for Scala
"select * from users where id=?"
or
def get(id: Long): User =
(for(u <- UserT...
More About Slick
• Using Options to represent NULLs
• Creates DDLs (tests without Play)
• Type safe in and out
• Syntax sa...
Type Safe Concurrency
abstract class RSession(roSession: => Session) extends SessionWrapper(roSession)
class ROSession(roS...
Using Sessions
db.readOnly { implicit s =>
// only reading from db - not in a transaction
val users = userRepo.getUsersByL...
DB Repo
trait Repo[M <: Model[M]] {
def get(id: Id[M])(implicit session: RSession): M
def all()(implicit session: RSession...
DB Repo Impl
trait DbRepo[M <: Model[M]] extends Repo[M] {
protected def table: RepoTable[M]
 
//you don’t have to run the...
DB Repo Impl - Persisting
def save(model: M)(implicit session: RWSession): M = try {
val toUpdate = model.withUpdateTime(c...
RepoTable
abstract class RepoTable[M <: Model[M]]
(db: DataBaseComponent, name: String) extends Table[M]
(db.entityName(na...
Example - User
case class User(
id: Option[Id[User]] = None,
createdAt: DateTime,
updatedAt: DateTime,
firstName: String,
...
Example - UserRepo
@ImplementedBy(classOf[UserRepoImpl])
trait UserRepo extends Repo[User] {
def usersWithLastName(lastNam...
Q & A
Binding (with Guice)
trait DbInfo {
def database: SlickDatabase
def driverName: String
}
 
class SlickModule(dbInfo: DbInf...
Upcoming SlideShare
Loading in …5
×

Using Scala Slick at FortyTwo

13,021 views

Published on

* Intro to Slick
* How we use it at FortyTwo
* Wrapping Sessions - Type Safe Concurrency
* Generic Access Layer

Published in: Technology, Education

Using Scala Slick at FortyTwo

  1. 1. Eishay Smith (CTO & Founder) @eishay of @42eng on @ 42go.com
  2. 2. Agenda • Intro to Slick • How we use it at FortyTwo • Wrapping Sessions • Generic Access Layer
  3. 3. About Slick Database query library for Scala "select * from users where id=?" or def get(id: Long): User = (for(u <- UserTable if u.id is id) yield u).first or def get(id: Id[M]): M = (for(f <- table if f.id is id) yield f).first Can do: insert, update, delete, DDL and more
  4. 4. More About Slick • Using Options to represent NULLs • Creates DDLs (tests without Play) • Type safe in and out • Syntax safe • Custom types • Can use regular sql • With type safe wrapping
  5. 5. Type Safe Concurrency abstract class RSession(roSession: => Session) extends SessionWrapper(roSession) class ROSession(roSession: => Session) extends RSession(roSession) class RWSession(rwSession: Session) extends RSession(rwSession) class Database @Inject() (val db: DataBaseComponent) {   import DBSession._   private def rawSession = db.handle.createSession()   def readOnly[T](f: ROSession => T): T = { val s = rawSession.forParams(rsConcurrency = ResultSetConcurrency.ReadOnly) try f(new ROSession(s)) finally s.close() }   def readWrite[T](f: RWSession => T): T = { val s = rawSession.forParams(rsConcurrency = ResultSetConcurrency.Updatable)) try { rw.withTransaction { f(new RWSession(s)) } } finally s.close() } }
  6. 6. Using Sessions db.readOnly { implicit s => // only reading from db - not in a transaction val users = userRepo.getUsersByLastName(“Smith”) accountRepo.getAccounts(users) } db.readWrite { implicit s => // reading and writing to db in a transaction val users = userRepo.getUsersByLastName(“Smith”) accountRepo.deleteAccounts(users) userRepo.markUsersAsInactive(users) } db.readOnly { implicit s => val user = userRepo.get(userId) emailRepo.updateEmail(user, newMail) <-- FAIL }
  7. 7. DB Repo trait Repo[M <: Model[M]] { def get(id: Id[M])(implicit session: RSession): M def all()(implicit session: RSession): Seq[M] def save(model: M)(implicit session: RWSession): M def count(implicit session: RSession): Int. def page(page: Int = 0, size: Int = 20) (implicit session: RSession): Seq[M] }
  8. 8. DB Repo Impl trait DbRepo[M <: Model[M]] extends Repo[M] { protected def table: RepoTable[M]   //you don’t have to run the whole Play Application to get the db setup! def descTable(): String = db.handle.withSession { table.ddl.createStatements mkString "n" }   def count(implicit session: RSession): Int = Query(table.length).first   def get(id: Id[M])(implicit session: RSession): M = (for(f <- table if f.id is id) yield f).first   def all()(implicit session: RSession): Seq[M] = table.map(t => t).list def page(page: Int = 0, size: Int = 20)(implicit session: RSession): Seq[M] = { val q = for { t <- table } yield t q.sortBy(_.id desc).drop(page * size).take(size).list } }
  9. 9. DB Repo Impl - Persisting def save(model: M)(implicit session: RWSession): M = try { val toUpdate = model.withUpdateTime(clock.now) val result = model.id match { case Some(id) => update(toUpdate) case None => toUpdate.withId(insert(toUpdate)) } } catch { case t: SQLException => throw new SQLException(s"error persisting $model", t) } private def insert(model: M)(implicit session: RWSession) = table.autoInc.insert(model)   private def update(model: M)(implicit session: RWSession) = { val target = for(t <- table if t.id === model.id.get) yield t val count = target.update(model) assert(1 == count) model }
  10. 10. RepoTable abstract class RepoTable[M <: Model[M]] (db: DataBaseComponent, name: String) extends Table[M] (db.entityName(name)) with TableWithDDL { //standardizing the following columns for all entities def id = column[Id[M]]("ID", O.PrimaryKey, O.Nullable, O.AutoInc) def createdAt = column[DateTime]("created_at", O.NotNull) def updatedAt = column[DateTime]("updated_at", O.NotNull) def autoInc = * returning id   //H2 likes its column names in upper case where mysql does not mind. //the db component should figure it out override def column[C : TypeMapper](name: String, options: ColumnOption[C]*) = super.column(db.entityName(name), options:_*) } }
  11. 11. Example - User case class User( id: Option[Id[User]] = None, createdAt: DateTime, updatedAt: DateTime, firstName: String, lastName: String ) extends Model[User] { def withName(firstName: String, lastName: String) = copy(firstName = firstName, lastName = lastName) }
  12. 12. Example - UserRepo @ImplementedBy(classOf[UserRepoImpl]) trait UserRepo extends Repo[User] { def usersWithLastName(lastName: String)(implicit session: RSession): Seq[User] } @Singleton class UserRepoImpl @Inject() (val db: DataBaseComponent, val clock: Clock) extends DbRepo[User] with UserRepo {   override val table = new RepoTable[User](db, "user") { def firstName = column[String]("first_name", O.NotNull) def lastName = column[String]("last_name", O.NotNull) def * = id.? ~ createdAt ~ updatedAt ~ firstName ~ lastName <> (User.apply _, User.unapply _) }   def usersWithLastName(lastName: String)(implicit session: RSession): Seq[User] = (for (u <- table if u.lastName = lastName yield u).list }
  13. 13. Q & A
  14. 14. Binding (with Guice) trait DbInfo { def database: SlickDatabase def driverName: String }   class SlickModule(dbInfo: DbInfo) extends ScalaModule { def configure(): Unit = { lazy val db = dbInfo.driverName match { case MySQL.driverName => new MySQL(dbInfo.database) case H2.driverName => new H2(dbInfo.database) } bind[DataBaseComponent].toInstance(db) bind[Database].in(classOf[Singleton]) } }

×