Here at Lucid Software we use a hypermedia-driven application architecture. This means that a client that uses our micro-services simply has to hard-code a “bootstrap” resource URI, and we can re-jigger our endpoints whenever we choose. This approach gives us flexibility on the back-end while maintaining perfect compatibility with our web app, iOS app, and other clients. The following is an example response for a hypermedia resource describing a document in our system:

{ "attributes": "https://documents.lucidchart.com/documents/ff0069a7-bf80-4f29-bdf0-aeb6cc8da593/attributes", "created": "2015-07-16T04:46:35Z", "creator": "https://users.lucidchart.com/users/102485935", "documentUsers": "https://documents.lucidchart.com/documents/ff0069a7-bf80-4f29-bdf0-aeb6cc8da593/users", "edit": "https://www.lucidchart.com/documents/edit/ff0069a7-bf80-4f29-bdf0-aeb6cc8da593", "id": "ff0069a7-bf80-4f29-bdf0-aeb6cc8da593", "invitations": "https://documents.lucidchart.com/documents/ff0069a7-bf80-4f29-bdf0-aeb6cc8da593/invitations", "pages": "https://documents.lucidchart.com/documents/ff0069a7-bf80-4f29-bdf0-aeb6cc8da593/pages", "saved": "2016-08-17T16:33:58Z", "size": 241278, "template": "https://documents.lucidchart.com/documents/ff0069a7-bf80-4f29-bdf0-aeb6cc8da593/template", "thumb": "https://www.lucidchart.com/documents/thumb/ff0069a7-bf80-4f29-bdf0-aeb6cc8da593/0/241278/NULL/400", "title": "Basic Use Case Diagram", "uri": "https://documents.lucidchart.com/documents/ff0069a7-bf80-4f29-bdf0-aeb6cc8da593", "usersWithAccess": "https://documents.lucidchart.com/documents/ff0069a7-bf80-4f29-bdf0-aeb6cc8da593/users/access" }

Most of these links are never used all at once. A typical client would receive a response and only follow the links they’re looking for.

Our mission at Lucid Software is to make work better, and we have a high standard for our application performance. We recently eliminated two common inefficiencies from our RESTful services.

1. java.net.URI is slow. We can get faster type safety.

Below is an example of a RESTful link class containing a URI as its only member. A typical REST resource would have many similar members.

case class FooLink(uri: java.net.URI)

java.net.URI’s constructor uses a parser to check the validity of the string used to instantiate it. The URI parser is useful for validating hyperlinks to catch errors before attempting HTTP requests. However, given enough URIs, instantiation time can add up. We noticed this when we were investigating timing spikes on one of our REST endpoints. Twenty links times thousands of users was costing us a significant amount of server power to compute:

Consequently, we chose to do away with the URI parser and other overhead of java.net.URI. We replaced it with a new class, a FastURI that basically wraps a String. We control the generation of the links on the back-end, so we can guarantee that they conform to the pattern for which the java.net.URI parser was testing.

case class FastURI(uri: String) case class FooLink(uri: FastURI)

On the rare occasion where we need to validate a hyperlink, we make a new URI(FastURI.uri) . Type safety is preserved with the FastURI type, and we still get the benefits of the URI parser in java.net.URI, but only when we need it. We use Scala, but this principle will apply to any JVM language.

2. Play’s functional syntax for JSON combinators may be elegant, but it’s also costly.

Another choke point for our endpoints was our JSON serialization. We use a fork of Play 2.3.8, and we discovered some unnecessary work being done by Play’s api.libs.functional.syntax._ when used to generate a JSON serializer. A common Play JSON formatter looks like this:

case class Foo ( do: FooLink, re: FooLink, mi: FooLink, fa: FooLink ) object Foo { implicit val format = Format[Foo] ( (JsPath / “do”) and (JsPath / “re”) and (JsPath / “mi”) and (JsPath / “fa”) ) (apply, unlift(unapply)) }

The “and” operators dynamically nest functions inside each other every time the formatter is used. Doing so is powerful in that it allows Play to serialize complex, recursive structures; however, it costs extra time and is not necessary for REST objects made up of links and simple object data. It is more efficient to specify the JSON marshaller and the JSON parser separately without the functional syntax, as shown below:

implicit val reads: Reads[Foo] = Reads[Foo] { json => for { do <- (json \ "do").validate[FooLink] re <- (json \ "re").validate[FooLink] mi <- (json \ "mi").validate[FooLink] fa <- (json \ "fa").validate[FooLink] } yield { Foo(do, re, mi, fa) } } implicit val writes: Writes[Foo] = Writes[Foo] { foo => Json.obj( "do" -> foo.do, "re" -> foo.re, "mi" -> foo.mi, "fa" -> foo.fa ) } implicit val format = Format(reads,writes)

This code avoids the layered functional syntax and performs much faster. It is twice as long, so we created a scala macro to generate the boilerplate code in one line. Play also provides a macro for creating JSON combinators, but it is important to note that the Play macro generates code with the slower functional syntax.

Result: a 1.5x improvement.

As a result of our tackling these two inefficiencies, we saw around a 50% decrease in the time it takes to create and serialize our REST objects to JSON. This figure is the result of a benchmark of a REST object with ten links tested before and after these optimizations. In addition, we saw great improvement in the endpoint that originally caught our attention. This graph shows the weeks before and after we released the change:

As we grow and serve an ever-increasing user base, it is important to consider our scalability. We are always searching for slow code that we can refactor before it wakes up our Ops team in the middle of the night due to a service that is overburdened and timing out. And most importantly, we want our services to be completely dependable as more and more people share their ideas visually with Lucidchart and Lucidpress.