Sign in to follow this  
  • entries
    3
  • comments
    0
  • views
    3631

Slick: Persistence with Style

Sign in to follow this  

976 views

[font='times new roman']

What is Slick?

[/font]
[font='times new roman']

Slick is a Functional Relational Mapper (FRM as oppose to the commonly used ORM) that allows us to compose type-safe database queries. Slick also allows us to interact with our database in the same way we would with a Scala collection; so no more raw SQL.

[/font]

[font='times new roman']

What's most important though, is that execution of our queries on our database is done asynchronously. This makes Slick a perfect fit for our Akka server.

[/font]

[font='times new roman']

Does Slick play well with PostgreSQL?

[/font]
[font='times new roman']

Yes. Slick supports a multitude of relational database management systems; but I prefer to work with PostgreSQL because it's open-source.

[/font]

[font='times new roman']

Slick in action

[/font]
[font='times new roman']

In my previous journal entry we saw our LoginHandler actor handle incoming messages and use a UserDAO object to do look ups and process user registrations. Let's take a closer look at our UserDAO class.

[/font][code=:0]package com.vinctus.venatus.daoimport scala.concurrent.{Future, Await}import scala.util.{Try, Success, Failure}import scala.concurrent.ExecutionContext.Implicits.globalimport java.util.UUIDimport java.sql.Timestampimport org.mindrot.jbcrypt._import slick.driver.PostgresDriver.api._import com.vinctus.venatus.models.Userclass UserDAO extends DB { private val Users = TableQuery[UsersTable] def list: Future[Seq[User]] = db.run(Users.result) def find(id: UUID): Future[Option[User]] = { db.run(Users.filter(_.id === id).result.headOption) } def find(email: String): Future[Option[User]] = { db.run(Users.filter(_.email === email).result.headOption) } def create(user: User): Future[Option[User]] = { val pwHash = BCrypt.hashpw(user.password, BCrypt.gensalt()) db.run((Users += user.patch(password = Some(pwHash))).asTry).flatMap({ case Success(result) => find(user.id) case Failure(e) => Future(None) }) } def update(user: User): Future[Option[User]] = { db.run(Users.filter(_.id === user.id) .map(u => (u.email, u.firstName, u.lastName)) .update(user.email, user.firstName, user.lastName).asTry) .flatMap(_ match { case Success(result) if result == 1 => find(user.id) case Failure(e) => Future(None) }) } def delete(id: UUID): Future[Int] = db.run(Users.filter(_.id === id).delete) private class UsersTable(tag: Tag) extends Table[User](tag, "users") { def id = column[UUID]("id") def email = column[String]("email", O.PrimaryKey) def password = column[String]("password") def firstName = column[Option[String]]("first_name") def lastName = column[Option[String]]("last_name") def createdAt = column[Timestamp]("created_at") def updatedAt = column[Timestamp]("updated_at") def idIdx = index("users_id_index", id, unique = true) def emailIdx = index("users_email_index", email, unique = true) def * = (id, email, password, firstName, lastName, createdAt, updatedAt) <> ((User.apply _).tupled, User.unapply _) }}
[font='times new roman']

Here we can see the find by email method I was using in LoginHandler to provide a user for user authentication.

[/font][code=:0]def find(email: String): Future[Option[User]] = { db.run(Users.filter(_.email === email).result.headOption)}
[font='times new roman']

This method is non-blocking and promises a Future[Option[User]]. Our User is a Scala case class and looks like:

[/font][code=:0]package com.vinctus.venatus.modelsimport java.util.UUIDimport java.sql.Timestampimport java.util.Datecase class User( id: UUID = UUID.randomUUID, email: String, password: String, firstName: Option[String] = None, lastName: Option[String] = None, createdAt: Timestamp = new Timestamp(new Date().getTime), updatedAt: Timestamp = new Timestamp(new Date().getTime)) { def patch( email: Option[String] = None, firstName: Option[String] = None, lastName: Option[String] = None, password: Option[String] = None): User = this.copy( email = email.getOrElse(this.email), password = password.getOrElse(this.password), firstName = if (firstName.isDefined) firstName else this.firstName, lastName = if (lastName.isDefined) lastName else this.lastName, updatedAt = new Timestamp(new Date().getTime))}
[font=tahoma][font='times new roman']

Conclusion

[/font][/font]
[font='times new roman']

We now have a basic skeleton for processing messages from the client that can persist data to the database when needed. From this point on we'll be adding more logic to handle more game features and also introduce Actors, DAOs, and domain models where needed to flesh out the full game server.

[/font]

Sign in to follow this  


0 Comments


Recommended Comments

There are no comments to display.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now