//Karthik Srinivasan

Product Engineer, CTO & a Beer Enthusiast
Experiments, thoughts and scripts documented for posterity.

Quirky Personal Projects

LinkedIn

Email me

Spray.io REST service - API Versioning - Lesson 6

View the lessons list at https://github.com/karthik20522/SprayLearning

Before we get into spray, would recommend reading different ways of Versioning an API or best practices of versioning an API at best-practices-for-api-versioning or versioning-rest-api.

To summarize the stackoverflow discussion, there are 3 ways to do versioning. For this tutorial, I would be implementing the first two.

1) Header based - using X-API-Version Here I am building a Directive that extracts the version from the request header. If not exists than I am defaulting to 1
    trait VersionDirectives {
      val extractVersion: Directive[String :: HNil] =
        extract { ctx =>
          val header = ctx.request.headers.find(_.name == "X-API-Version")
          header match {
            case Some(head) => head.value
            case _          => "1" //default to 1
          }
        }

      def versioning: Directive[String :: HNil] =
        extractVersion.flatMap { v =>
          provide(v)
        }
    }
In the above code there are two keywords, "extract" and "provide". These are part of Sprays BasicDirective. "extract" basically allows you to extract a single value and "provides" allows you to inject a value into the Directive. But wait, we can make this trait even smaller by getting rid of the provide all together to something as follows:
trait VersionDirectives {
  def versioning: Directive[String :: HNil] =
    extract { ctx =>
      val header = ctx.request.headers.find(_.name == "X-API-Version")
      header match {
        case Some(head) => head.value
        case _          => "1" //default to 1
      }
    }
}

Note: there are more than one way to write same operation in scala/spray. bane of my existence!
More info at: spray/routing/directives/BasicDirectives.scala

Now that we have defined the directive, we just need to inherit the directive to service trait and call versioning to extract the version number from X-API-Version header field.
    trait CustomerService extends HttpService with Json4sSupport with VersionDirectives {

    val customerRoutes = {
    path("getCustomer" / Segment) { customerId =>
    get {
    versioning { v =>
    println(v)
    //do something now that you have extracted the version number
    . . . .
2) url based - http://{uri}/v1/getCustomer
Here we are basically performing regex on the incoming request.uri and extracting the version out of "v*". Spray provides quite a few PathFilters and one of them being PathMatcher. More info at:
trait CustomerService extends HttpService with Json4sSupport {

  val Version = PathMatcher("""v([0-9]+)""".r)
    .flatMap {
      case vString :: HNil => {
        try Some(Integer.parseInt(vString) :: HNil)
        catch {
          case _: NumberFormatException =>
            Some(1 :: HNil) //default to version 1
        }
      }
    }

  val customerRoutes =
    pathPrefix(Version) { apiVersion =>
      {
        //. . . .
        path("getCustomer" / Segment) { customerId =>
          get {
            complete {
              apiVersion match {
                case 1 => {
                  // do something if version 1
                }
                case 2 => ??? //do something if version 2
                case _ => {
                  //do something if any other version
                }
              }
            }
          }
        }
      }
    }
}