Building applications with Scala, Play and Angular

facebooktwittergoogle_pluslinkedin

After working with the Play framework a lot (version 1.x though, with Java) at University and taking the Scala course with Odersky on Coursera, I’ve been wanting to try out Play 2.x with Scala for a long time. The last couple of days I spent wading through various tutorials until I finally got my first Scala based Play application on Heroku working. Now I’d like to share some experiences.

Technologies

So while the first two are fairly standard when working with play, the latter two require some further explanation. I do not use the whole view part of the Play! Framework. When using a template engine, the view is dependent on the controller. The controller does not only answer with the raw data, but also how the data is rendered, so the view part. So although the core feature of MVC, the ability to exchange the view part without caring about the rest, is still maintained, it doesn’t work the other way around. This way you can test the client without any part of the application server. Especially angular offers various ways to do that.

But this is certainly a question of personal taste. What I’ve seen of the template engine of the Play framework, it seems quite powerful and easy to use (given you have basic Scala knowledge, even when you are using Play with Java).

I’m not going to show the view part, as I have written about Angular quite a lot already.

Backend with Play 2.0

So, a typical model in Play 2.0 with Scala looks like this:

case class League(id: Long, title: String, desc: Option[String], startDate: LocalDate, endDate: LocalDate)

object League {
  val league: RowParser[League] = {
    get[Long]("id") ~
      get[String]("title") ~
      get[Option[String]]("desc") ~
      get[LocalDate]("startDate") ~
      get[LocalDate]("endDate") map {
      case id ~ title ~ desc ~ startDate ~ endDate => League(id, title, desc, startDate, endDate)
    }
  }

  def all(): List[League] = DB.withConnection {
    implicit c => SQL("select * from league").as(league *)
  }

  def single(id: Long): League = DB.withConnection {
    implicit c => SQL("select * from league where id = {id}").on('id -> id).single(league)
  }

  def find(id: Long): Option[League] = DB.withConnection {
    implicit c => SQL("select * from league where id = {id}").on('id -> id).singleOpt(league)
  }

  def create(title: String, desc: Option[String], startDate: LocalDate, endDate: LocalDate): Option[Long] = DB.withConnection {
    implicit c => SQL("insert into league(title, desc, startDate, endDate) " +
      "values ({title}, {desc}, {startDate}, {endDate})").on(
      'title -> title,
      'desc -> desc.getOrElse(null),
      'startDate -> startDate,
      'endDate -> endDate
    ).executeInsert()
  }

  implicit object LeagueFormat extends Format[League] {
    def reads(json: JsValue): JsSuccess[League] = JsSuccess(League(
      (json \ "id").as[Long],
      (json \ "title").as[String],
      (json \ "desc").asOpt[String],
      new LocalDate((json \ "startDate").as[DateTime]),
      new LocalDate((json \ "endDate").as[DateTime])))

    def writes(l: League): JsValue = {
      JsObject(Seq(
        Some("id" -> JsNumber(l.id)),
        Some("title" -> JsString(l.title)),
        l.desc.map("desc" -> JsString(_)),
        Some("startDate" -> JsString(l.startDate.toString)),
        Some("endDate" -> JsString(l.endDate.toString))
      ).filter(_.isDefined).map(_.get))
    }
  }
}

So, to explain that a bit: First you declare your case class, pretty straight-forward. This is the actual model.

The rest is the data-access-object (DAO) which accesses the database with anorm. The first thing you need for that is a parser. This is the value at the top of the object, the row parser. It defines how to interpret a row from the database and how to transform it to a Scala object.

Then follow the actual operations to access the database. When coming from the Java world with object-relational mappers like JPA, writting actual sql queries might look a bit like the stone age to you, but I actually like the approach. The argument can be found on the anorm page:

SQL is already the best DSL to access relational Databases. We don’t need to invent something new.

I like this point a lot. In previous projects I had to find several ways to bypass JPA anyway due to the complex model.

The LeagueFormat I defined looked a  bit like magic to me in the beginning (only because I’m not yet used enough to implicit parameters). The object at the bottom is used to serialize and deserialize my objects to JSON.

I like especially the way how it is written to JSON. The model has exactly one optional value called desc. So in the beginning the JSON object just had an empty string when this value was not set. But a proper use of the optional value the JSON object now doesn’t even have the attribute at all. In my first stab at implementing LeagueFormat.writes, I just mapped an absent description to the empty string. This meant that the resulting JSON object still had an entry for desc, which, when we want to reverse the conversion, would result in an empty but defined desc value. I ended up with the definition of LeagueFormat.writes you see above. (Using map on a type that doesn’t look like a collection at all looked strange at first, but makes sense after a while . For more information take a look at Monad.)

Anorm extension

The above code will not work flawlessly though. Anorm doesn’t have a converter for the joda.time formats like LocalDate. But with the help of Stackoverflow and Google I was able to write the following piece of code, which solves the problem:

object AnormExtension {
  implicit def rowToLocalDate: Column[LocalDate] = Column.nonNull {
    (value, meta) =>
      val MetaDataItem(qualified, nullable, clazz) = meta
      value match {
        case ts: java.sql.Timestamp => Right(new LocalDate(ts.getTime))
        case d: java.sql.Date => Right(new LocalDate(d.getTime))
        case str: java.lang.String => Right(dateFormatGeneration.parseLocalDate(str))
        case _ => Left(TypeDoesNotMatch("Cannot convert " + value + ":" + value.asInstanceOf[AnyRef].getClass))
      }
  }

  implicit def localDateToStatement: ToStatement[LocalDate] = new ToStatement[LocalDate] {
    def set(s: java.sql.PreparedStatement, index: Int, aValue: LocalDate) {
      s.setDate(index, new java.sql.Date(aValue.toDate.getTime))
    }
  }
}

The extension simply defines two functions. The first one takes the value of a column and tries to convert it to joda.time.Localdate. The second one writes a LocalDate value to a sql date value.

Credit for this piece of code goes to mchv on stackoverflow.

Serving JSON responses

So that we have implemented the model we only have to write controllers to handle requests and replying with JSON responses. That looks like that for example:

import JsonResponse._

object LeagueCtrl extends Controller {
  def all = Action {
    success(League.all())
  }
}

To make the conversion to Json a bit easier, I created the following helper object JsonResponse. It mainly consists of the success function, which takes some data and an implicit json converter and writes the data with this converter as a HTTP response.

case class Error(code: Long, msg: String)

object JsonResponse extends Results {
  implicit object ErrorResponseFormat extends Format[Error] {
    // The following function is not defined, as I do not expect to read error messages from json
    def reads(json: JsValue): JsResult[Error] = ???

    def writes(r: Error): JsValue = JsObject(Seq(
      "code" -> JsNumber(r.code),
      "msg" -> JsString(r.msg)
    ))
  }

  def success[T](data: T)(implicit tjs: Writes[T]) = Ok(toJson(tjs.writes(data)))

  // ...

  val NoSuchElement = NotFound(toJson(Error(34, "Sorry, this entity does not exist")))
}

Pretty simple, isn’t it?

4 thoughts on “Building applications with Scala, Play and Angular

  1. Kudos first: fantastic tutorial !
    I was wondering: any particular reason not to use the slick instead of Anorm ?
    Cheers

    • Glad you liked it! To answer your question: Yeah, rather a simple reason: I don’t know Slick. I’ve quickly reviewed in once but then decided to go with the technology which is also used by the Play! Framework. But I don’t have enough knowledge to directly compare them both.

  2. Super Bispel, Merci..

    Are you still using this setup or any new insights/adjustments to your web-“stack”?

    Thanks,
    Alessandro

Comments are closed.