Posts Sangria without case classes
Post
Cancel

Sangria without case classes

In my previous post I talked about how easy it is to implement a dynamically generated GraphQL Schema using graphql-js as you can simply ignores types and do your job. I then made some comments about Sangria, perhaps the most stable and powerful GraphQL lib for Scala. Follow me in this exploration until I find a solution or a good soul can share it with us.

If you refer to the sangria-akka-http-example project, you’ll see the hand made schema. Something like:

// Data.scala

trait Character {
  def id: String
  def name: Option[String]
  def friends: List[String]
  def appearsIn: List[Episode.Value]
}

case class Droid(
                  id: String,
                  name: Option[String],
                  friends: List[String],
                  appearsIn: List[Episode.Value],
                  primaryFunction: Option[String]) extends Character

// SchemaDefinition.scala

  val Droid = ObjectType(
    "Droid",
    "A mechanical creature in the Star Wars universe.",
    interfaces[CharacterRepo, Droid](Character),
    fields[CharacterRepo, Droid](
      Field("id", StringType,
        Some("The id of the droid."),
        tags = ProjectionName("_id") :: Nil,
        resolve = _.value.id),
      Field("name", OptionType(StringType),
        Some("The name of the droid."),
        resolve = ctx  Future.successful(ctx.value.name)),
      Field("friends", ListType(Character),
        Some("The friends of the droid, or an empty list if they have none."),
        resolve = ctx  characters.deferSeqOpt(ctx.value.friends)),
      Field("appearsIn", OptionType(ListType(OptionType(EpisodeEnum))),
        Some("Which movies they appear in."),
        resolve = _.value.appearsIn map (e  Some(e))),
      Field("primaryFunction", OptionType(StringType),
        Some("The primary function of the droid."),
        resolve = _.value.primaryFunction)
    ))

Here the Droid ObjectType uses a Droid (a case class) as its internal representation. In order to circunvent such way to define GraphQL Object Types, I tried many things using shapeless.

To be fair, I’m far from being proficient in shapeless. And the only solution I found so far was to use Records. This is a half solution because even using it, I had to set up the Record by hand. Look:

// SchemaDefinition.scala

  type Book = Record.`'year -> Int, 'title -> String, 'available -> Boolean`.T

  val BookType = ObjectType(
    "Book",
    "sample book",
    fields[CharacterRepo, Book](
      Field("title", StringType,
        Some("Book Title"),
        resolve = ctx  ctx.value('title)),
      Field("year", IntType,
        Some("Year of publication"),
        resolve = ctx  ctx.value('year)),
      Field("available", BooleanType,
        Some("Indicates if available"),
        resolve = ctx  ctx.value('available))
    ))

  val Query = ObjectType(
    "Query", fields[CharacterRepo, Unit](
      Field("book", BookType,
        resolve = ctx  Record(year = 2012, title = "Land of Lisp",  available = true)),
      Field("hero", Character,

There is a powerful macro here responsible for generating the type of our book. But I still couldn’t find a way to generate the Book type at runtime. Bear the resolve here is hard coded instead of bringing something from the database, for example. Enough for our purpose though.

We were able to replace a case class by a shapeless Record and I think in few more tries we can replace everything by HLists. In fact if you create a instance of Book, you’ll see the type as a HList with tagged type parameters and - not sure - some singleton types for the field labels.

scala> val b = Record(year = 2012, title = "Land of Lisp",  available = true)
b: shapeless.::[shapeless.labelled.FieldType[shapeless.tag.@@[Symbol,String("year")],Int],
shapeless.::[shapeless.labelled.FieldType[shapeless.tag.@@[Symbol,String("title")],String],
shapeless.::[shapeless.labelled.FieldType[shapeless.tag.@@[Symbol,String("available")],Boolean],shapeless.HNil]]] 
= 2012 :: Land of Lisp :: true :: HNil

In the end, the problem is not only replacing case classes for something more dynamic, but the Sangria Field and ObjectType are quite annoying to instantiate if you start moving things around and for example don’t use fields function from sangria.schema package object.

Conclusion

While the solution in NodeJs or clojure would be simply replace the thing by maps, using Map[String, Any] in scala is not only bizarre but would led to a confusing code full of type casts and exception prone. Shapeless comes to the rescue and allows for elegant, abstract generic approach while keeping your code safe.

Ok, good results so far. I’m using this challenge to (re)warm my mind around Scala. My last and sole system in production written in scala was released almost two years ago and it is wise to refresh knowledge from time to time.

My next steps includes getting some inspiration from Underscore.io using Using shapeless HLists with Slick 3 blog post as basis and studying more about shapeless using the Underscore.io book and some E.near blog posts.

I wouldn’t be surprised if a experienced shapeless professional drop a comment and say: hey newbie, just do this and that :). Nonetheless, I’m enjoying the path to solve the case. If you know how to solve it or need more context on what I’m trying to do, send me a email or leave a comment.

Happy shapeless!

This post is licensed under CC BY 4.0 by the author.