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

12,773 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]) } }

×