A Simple REST DSL Part 2

Using a builder to construct a DSL.

Posted by Iain Hull on July 2, 2013

In part 1, I showed the builder pattern can improve code readability and composibility of REST requests. Now, let's discover how the builder can be used as the basis for a simple DSL.

First you need to decide what words or phrases to use to bootstrap the request DSL. Do this by describing some sample requests in simple English and looking for patterns, for example:

  • Get from url http://api.rest.org/person/
  • Post personJson to url http://api.rest.org/person/
  • Get from url http://api.rest.org/person/personId
  • Delete from url http://api.rest.org/person/personId

All of these start with the http method so this is the ideal place to start the DSL. You can define an implicit function to convert a Method object into a RequestBuilder. Now when a RequestBuilder method is called on a Method object, the implicit function is called to convert it to a RequestBuilder first.

implicit def methodToRequestBuilder(method: Method): RequestBuilder = RequestBuilder().withMethod(method)

Now you can rewrite the following expression

driver.execute(RequestBuilder().withMethod(GET).withUrl("http://api.rest.org/person/"))

with a simple DSL as follows

driver.execute(GET withUrl "http://api.rest.org/person/")

You can improve he readability of the use case using this simple DSL and Scala's infix notation, as follows.

val personJson = """{ "name": "Jason" }"""
val r1 = driver.execute(GET withUrl "http://api.rest.org/person/")
val r2 = driver.execute(POST withUrl "http://api.rest.org/person/" withBody personJson)
val id = r2.headers.get("X-Person-Id").get.head
val r3 = driver.execute(GET withUrl "http://api.rest.org/person/" addPath id)
val r4 = driver.execute(GET withUrl "http://api.rest.org/person/")
val r5 = driver.execute(DELETE withUrl "http://api.rest.org/person/" addPath id)
val r6 = driver.execute(GET withUrl "http://api.rest.org/person/")

Now, let's try to simplify the driver.execute part of the code. Again implicit conversions enable you to automatically add an execute method to the RequestBuilder when required.

implicit class RichRequestBuilder(builder: RequestBuilder) {
  def execute()(implicit driver: Driver): Response = {
    driver.execute(builder)
  }
}

When this class is in scope any RequestBuilder objects will be automatically be converted to RichRequestBuilder objects when the execute method is called. This technique is the Pimp My Library Pattern and is heavily use in Scala to safely extend existing classes. Implicit classes are a new feature in Scala 2.10. They are shorthand for a class definition and an implicit function to convert the class' parameters to an instance of the class.

Now the use case reads much more like English. The driver is passed to the execute method implicitly removing some more boiler-plate code. However, because the method now takes no parameters empty parentheses are required with infix notation to enable the Scala compiler to find the end of the expression. This also matches Scala's style of using empty parentheses to highlight side effects.

implicit val driver = ...
val personJson = """{ "name": "Jason" }"""
val r1 = GET withUrl "http://api.rest.org/person/" execute ()
val r2 = POST withUrl "http://api.rest.org/person/" withBody personJson execute ()
val id = r2.headers.get("X-Person-Id").get.head
val r3 = GET withUrl "http://api.rest.org/person/" addPath id execute ()
val r4 = GET withUrl "http://api.rest.org/person/" execute ()
val r5 = DELETE withUrl "http://api.rest.org/person/" addPath id execute ()
val r6 = GET withUrl "http://api.rest.org/person/" execute ()

Wouldn't it be nice not to have to duplicate the withUrl on every line? Maybe you can put this outside a code block and pass the url in implicitly:

val personJson = """{ "name": "Jason" }"""
RequestBuilder() withUrl "http://api.rest.org/person/" apply { implicit rb =>
  val r1 = GET execute ()
  val r2 = POST withBody personJson execute ()
  val id = r2.headers("X-Person-Id").head
  val r3 = GET addPath id execute ()
  val r4 = GET execute ()
  val r5 = DELETE addPath id execute ()
  val r6 = GET execute ()
}

To support this you must change the DSL, so that each new expression will use the closest implicit RequestBuilder object in scope. This will enable partial RequestBuilder objects be used as the starting point for later expressions.

First add implicit support to the RequestBuilder helper object. The emptyBuilder is defined as implicit, now if there are no implicit RequestBuilder objects in scope, the empty one will be used. Next, add an implicit second argument list to the apply method, this now simply returns the implicit builder. The end result is that the expression RequestBuilder() will always return the correct object using implicit resolution.

object RequestBuilder {
  implicit val emptyBuilder = RequestBuilder(None, None, Seq(), Seq(), None)

  def apply()(implicit builder: RequestBuilder): RequestBuilder = {
    builder
  }
}

The same technique can be used with the methodToRequestBuilder function. Now, instead of always using the emptyBuilder, it adds the method to the latest one using implicit resolution.

implicit def methodToRequestBuilder(method: Method)(implicit builder: RequestBuilder): RequestBuilder = builder.withMethod(method)

Now that you can use codeblocks to reuse common request parameters it would be nice to simplify the DSL by adding nicer aliases for some of the common RequestBuilder methods.

implicit class RichRequestBuilder(builder: RequestBuilder) {
  def url(u: String) = builder.withUrl(u)
  def body(b: String) = builder.withBody(b)
  def /(p: Symbol) = builder.addPath(p.name)
  def /(p: Any) = builder.addPath(p.toString)
  def :?(params: (Symbol, Any)*) = builder.addQuery(params map (p => (p._1.name, p._2.toString)): _*)

  // ...
}

This enables you to create the more descriptive version or our use case below. The / method is shorthand for addPath and the :? method is shorthand for addQuery. It is unfortunate that the ":" is required to preserve operator precedence rules in Scala. Methods starting with ? have a higher precedence to other methods, adding the ":" reduces the precedence so the expressions works as expected.

val personJson = """{ "name": "Jason" }"""
RequestBuilder() url "http://api.rest.org/" apply { implicit rb =>
  val r1 = GET    / 'person execute ()
  val r2 = POST   / 'person body personJson execute ()
  val id = r2.headers("X-Person-Id").head
  val r3 = GET    / 'person / id execute ()
  val r4 = GET    / 'person execute ()
  val r5 = DELETE / 'person / id execute ()
  val r6 = GET    / 'person :? ('page -> 2, 'per_page -> 100) execute ()
}

This shows how easy it is to create a simple DSL based the Pimp My Library Pattern and a few implicit conversions. The intent of each expression is made more explicit by removing the boiler-plate code and simplifying the syntax so only information that affects the meaning of the expression is used.

The source code for this post is available on github

Next I will examine extending the DSL to extract specific values and verifying parts of the response.

If you would like to give me any feedback regarding this post I am happy to discuss anything and everything on Twitter and email. If there is enough interest I will also consider enabling comments on my blog.