1

I am working on a REST-API implemented in Scalatra and using reactivemongo. My persistent model is implemented using case classes and a thin repository layer uses the common approach for bson<->class mapping via DocumentReader/DocumentWriter (via implicit converters in case class companion object).

case class Address(street: String, number: String, city: String, zip: String)
object Address{

implicit val addressHandler = Macros.handler[Address]
implicit val addressFmt = Json.format[Address]
}

The first formatter maps bson to case classes the second converts to json (the output format of the REST-API).

This is all fine and I am quite happy with how nicely everything integrates.

In many cases though, I don't need to operate on the domain objects (case class instances) and just want to stream the data coming from the data base write into the http response. All the intermediate conversions are overhead in those scenarios. I also want to control which fields are exposed (I once used Yoga and Jackson in a Java project).

Possible solutions for this would be to:

  • have a generic repository that simply converts to a Map structure as intermediate format.
  • control the implicit converters available to the driver on a per-query basis and write case classes for different "views"
  • use BSONDocument as intermediate format and make the REST layer understand BSON via a bson=>string conversion.

I wonder what is the best approach and if anybody has some experience with that particular scenario. Maybe I am even missing out another good option? Feedback is very welcome.

Yathish Manjunath
  • 1,919
  • 1
  • 13
  • 23
bennidi
  • 2,092
  • 21
  • 27
  • You can do coast to coast using `JsValue` or `JsObject`. – cchantep Jul 30 '15 at 11:09
  • play-reactivemongo does something similar and I think you could re-use this idea – Barry Jul 30 '15 at 13:11
  • Coast-to-Coast (https://www.playframework.com/documentation/2.1.0/ScalaJsonTransformers) looks very promising. I just wonder how to reduce the custom code necessary to do that. Maybe one could implement a more user friendly DSL with pattern matching that translates to combinations of transformers. But that's the future :) @cchantep Maybe you could make your comment an answer - I might actually accept it :) – bennidi Jul 31 '15 at 08:51
  • You are looking at an old documentation. – cchantep Jul 31 '15 at 10:51

1 Answers1

0

To control which fields are exposed, you probably have to write a Readers and Writers. Below is an example extracted from this project https://github.com/luongbalinh/play-mongo.

import java.time.ZonedDateTime

case class User(override val id: Option[Long] = None,
            firstName: String,
            lastName: String,
            age: Int,
            active: Boolean,
            createdDate: Option[ZonedDateTime] = None,
            updatedDate: Option[ZonedDateTime] = None
             ) extends IdModel[User] {
  override def withNewId(id: Long): User = this.copy(id = Some(id))
}

object User {

import play.api.libs.functional.syntax._
import play.api.libs.json._

implicit val userReads: Reads[User] = (
(JsPath \ "id").readNullable[Long] and
  (JsPath \ "firstName").read[String] and
  (JsPath \ "lastName").read[String] and
  (JsPath \ "age").read[Int] and
  (JsPath \ "active").read[Boolean] and
  (JsPath \ "createdDate").readNullable[ZonedDateTime] and
  (JsPath \ "updatedDate").readNullable[ZonedDateTime]
)(User.apply _)

implicit val userWrites: Writes[User] = (
(JsPath \ "id").writeNullable[Long] and
  (JsPath \ "firstName").write[String] and
  (JsPath \ "lastName").write[String] and
  (JsPath \ "age").write[Int] and
  (JsPath \ "active").write[Boolean] and
  (JsPath \ "createdDate").writeNullable[ZonedDateTime] and
  (JsPath \ "updatedDate").writeNullable[ZonedDateTime]
)(unlift(User.unapply))
}
Luong Ba Linh
  • 802
  • 5
  • 20