Presentation given at JavaOne 2013 by Derrick Isaacson, Director of Development for Lucid Software, makers of Lucidchart. The presentation explains how the uniform interface of REST creates evolvable, versioned APIs. The presentation includes many examples and code snippets using Scala & Play.
5. Web Site or Web Service
• Servers return XML
• IDs -> more HTTP requests
• Auth tokens, caching, content type negotiation
• Error status codes –> retries
• More XML, JPEG, other formats
• Series of rendering steps
6. “There is no magic dust that
makes an HTTP request a web
service request.”
-Leonard Richardson & Sam Ruby, RESTful Web Services
12. REST Components
Data Element Modern Web Examples Versioning
resource the intended conceptual
target of a hypertext
reference
resource identifier URL, URN
representation HTML document, JPEG
image
representation metadata media type, last-modified
time
control data if-modified-since, cache-
control
13. 1. Evolving the Resource (not the
representation!)
1. Uniform identification of resources
2. Uniform resource manipulation
3. Representation separate from the identity
4. Hypermedia as the engine of application state
5. Self-descriptive messages
14. 1. Evolving the Resource
http://lucidchart.com/docs/MyHome/v1 http://lucidchart.com/docs/MyHome/v2
15. REST Components
Data Element Modern Web Examples Versioning
resource the intended conceptual
target of a hypertext
reference
URLs
resource identifier URL, URN
representation HTML document, JPEG
image
representation metadata media type, last-modified
time
control data if-modified-since, cache-
control
16. 2. Evolving the Resource Identifier
1. Uniform identification of resources
2. Uniform resource manipulation
3. Representation separate from the identity
4. Hypermedia as the engine of application state
5. Self-descriptive messages
19. 2. Evolving URLs & Uniform Response
Codes
http://lucidchart.com/imgs/123 http://images.lucidchart.com/imgs/123
200 OK
…
<response>
<status>Error</status>
<msg>Not Found</msg>
</response>
404 Not Found
…
<userMsg>
…
</userMsg>
301 Moved Permanently
Location: http://images...
…
20. 2. Hypermedia with Scala & Play
#routes
GET /users/{id} lucid.getUser(id)
def getUser(id: Int) = {
val accountURI =
routes.App.userAccount(id)
val template = User(accountURI)
Ok(template)
}
21. 2. Evolving URLs with Scala & Play
# Images moved permanently
GET /imgs/{id} lucid.redirectImages(id)
def redirectImages(id: Int) = {
val location =
“http://images.lucidchart.com/images/”+id
Redirect(location)
}
22. REST Components
Data Element Modern Web Examples Versioning
resource the intended conceptual target of a
hypertext reference
URLs
resource identifier URL, URN Hypermedia &
Uniform Status
Codes
representation HTML document, JPEG image
representation metadata media type, last-modified time
control data if-modified-since, cache-control
25. 3. Evolving Resource Metadata &
Control Data
1. Uniform identification of resources
2. Uniform resource manipulation
3. Representation separate from the identity
4. Hypermedia as the engine of application state
5. Self-descriptive messages
26. REST Components
Data Element Modern Web Examples Versioning
resource the intended conceptual target of
a hypertext reference
URLs
resource identifier URL, URN Hypermedia & Uniform
Status Codes
representation HTML document, JPEG image
representation metadata media type, last-modified time Self-descriptive w/
Uniform Headerscontrol data if-modified-since, cache-control
27. 4. Evolving Representations
GET /images/123
…
200 OK HTTP 1.1
Content-Type: application/xml
…
<response>
<status>success</status>
<id>123</id>
<image>MTIzNmEyMTM…=</image>
</response>
GET /images/123
Accept: image/jpeg
…
200 OK HTTP 1.1
Content-Type: image/jpeg
…
(jpeg image)
30. 4. Custom Media Types
GET /diagrams/v1/123 HTTP/1.1
…
GET /diagrams/v1/123
…
31. 4. Custom Media Types
GET /diagrams/123 HTTP/1.1
Accept: application/vnd.lucid.diagram+xml
…
GET /diagrams/123 HTTP/1.1
Accept: application/vnd.lucid.diagram-v2+xml
…
32. REST Components
Data Element Modern Web Examples Versioning
resource the intended conceptual target of
a hypertext reference
URLs
resource identifier URL, URN Hypermedia & Uniform
Status Codes
representation HTML document, JPEG image Content-type header,
uniform media types,
and general uniform
interface
representation metadata media type, last-modified time Self-descriptive w/
Uniform Headerscontrol data if-modified-since, cache-control
33. “The central feature that
distinguishes the REST
architectural style from other
network-based styles is its
emphasis on a uniform interface
between components. By applying
the software engineering principle
of generality to the component
interface, the overall system
architecture is simplified and the
visibility of interactions is improved.
Implementations are decoupled
from the services they provide,
which encourages independent
evolvability.”
Lucidchart is a collaborative diagramming SaaS application. This session’s speakers have been transitioning from a monolithic CakePHP application to a services-oriented RESTful system based solely on Scala. They discuss their decision to ditch PHP and how they ended up with a working set of services. First, they explain their reasoning for switching languages, runtimes, and frameworks. Then they show how to create basic RESTful services in Scala and Play!, including backward-compatible, versioned API endpoints. And finally, they reveal their future plans to integrate further with Scala and Play!.
1. Scaling a global web application: LucidchartSurvey captured data from customers in 107 countries with a combined 3.42 years in the Lucidchart document editor.
“Evolvability is defined as the capacity of a system for adaptive evolution. Evolvability is the ability of a population of organisms to not merely generate genetic diversity, but to generate adaptive genetic diversity, and thereby evolve through natural selection.” – Wikipedia (http://en.wikipedia.org/wiki/Evolvability)Evolvability is not merely the generation of diversity, but adapting to a changing environment.We’ll analyze:Scaling a global web application: LucidchartAn expansive perspective of web servicesWhat is and is not RESTVersioning web services with RESTImage: http://m.cacm.acm.org/news/164120-computer-scientists-suggest-new-spin-on-origins-of-evolvability/fulltext
Over the last 18 months Lucidchart has taken logical chunks of the system and rebuilt them in Scala on the Play Framework. Many of you could probably tell a similar story, but CakePHP got Lucidchart up and running quickly. Our unique intellectual property was 200k+ lines of JavaScript rendering logic. It worked for a while, but over time our features changed and our user base grew a lot. Cake’s server-side session state caused problems. It didn’t play well with multiple forms of authentication. The heuristics for URI routing pushed us into less effective designs, and the database models of cake similarly caused us performance and design issues.My colleague and our VP of engineering, Brian Pugh, will discuss the details of the technology decisions around Scala and Play in a session tomorrow at 3 p.m. For today it is only important that Play let us build more effective, evolvable services through RESTful designs.Through this server rewrite, and similar experiences at Amazon, Microsoft, and Domo, I’ve stubbed my toes many times. The scars challenged my assumptions and understanding of web services. I’ve found that in presenting about web services it’s most effective to first take an expansive view that sheds light on some common misconceptions. Then when we dive down into specific protocols and patterns we’ll have a better perspective for designing distributed systems.
We often classify systems as either web sites or web services. I’m going to walk through a brief description of a made-up mapping application, and as I do, think about which class of system it is.The map system contains a group of HTTP servers. This mapping app also contains a set of clients running on different machines. The map servers send XML to the clients containing a description of a map. Some XML elements contain IDs to other resources. A map client parses many of those IDs and makes HTTP requests to retrieve them. The requests usually include an authentication token, cache control instructions, content type negotiation, and other metadata. The responses include a variety of machine readable status codes. Some error status codes cause retries by the client, some cause a modified request to be made, and other errors include an XML element containing a user friendly message that the client parses to display to the user. Many of the resources requested come back formatted as XML, some are JPEG map tiles, and some are other formats. The client performs a complex rending pipeline to calculate an image of the map and other info, and then display it on the screen.Is this map server a web service or a web site?Both!
This is something I first learned reading Richardson & Ruby’s insightful “RESTful Web Services” book. There’s really nothing special about web services.HTTP is an application layer protocol. A common mistake that results in brittle, RPC-style web services is the use of HTTP as a transport layer for some other homegrown application protocol.
So, what is a web service then? Lucidchart is builton Amazon’s AWS infrastructure and other popular cloud services. Some of the web services I consumer regularly in my work are MySQL, through Amazon’s Relational Database Service, cloud logging services over UDP, hosted MongoDB services, DNS services, and cloud SMTP mail services.What’s common among all of these services? Just IP, the Internet Protocol, which is commonly called the “skinny waist” of the internet for this very reason.So, in architecting web services I find it effective to think of web services just as any IP based service.
If we’re going to get serious about versioned web APIs, how do we architect our system?DavidCheriton’s research projectPerformance problems. Error handling problems. I have never actually failed to read from memory.SOAPPerformance variation problems. 1 us vs 100 ms. My front side bus fails for a whole second every 17 minutes. Our internet only works in .NET. (Inspired by Distributed Systems Lecture Notes by David Cheriton)The WebWe’ll discuss this throughoutNFS, Andrew FS, Google FSVarious file system semanticsChord, Kademlia, BitTorrent, Skype, SpaceMonkeyGreat for avoiding the law or mooching bandwidth & electricity from universities.HTTP Live Streaming (HLS), DASHI love it. My iPad is just a glorified Netflix player.
DASH protocol
At Lucidchart we’re moving toward the REST architectural style, and one of the reasons is that it gives us an adaptable and evolvable architecture.In the year 2000 Roy Fielding published his dissertation “Architectural Styles and the Design of Network-based Software Architectures”. In it he studies various architectural styles and analyzes why the web was so successful.Roy surveyed these application architecture designs to discover which were effective at four specific goals:Low entry-barrierDistributed hypermediaInternet-scaleExtensibilityQuickly summarizing three of the goals:1. Looking at the goal low barrier to entry we see it is updated and managed by various entities independently, using many platforms, and being highly available to content authors and managers even when some pieces are down.2. Hypermedia requires that presentation and control info be transferred to and from a client. Roy analyzed why the web is so successful at creating a performant application with hypermedia.3. The Web’s accomplishment in achieving internet scale is incredible.The final goal of extensibility is where we’ll spend the rest of our time.
“While simplicity makes it possible to deploy an initial implementation of a distributed system, extensibility allows us to avoid getting stuck forever with the limitations of what was deployed. Even if it were possible to build a software system that perfectly matches the requirements of its users, those requirements will change over time just as society changes over time.” – Roy FieldingThe web has moved past the original server and client hardware. We moved past token ring networks and shared medium Ethernet to Wi-fi and cellular links. We’ve experimented with Java applets, Flash, and Silverlight. We constantly update Apache, Jetty, and IIS servers. Links change. Content changes. Content comes and goes. Representations of resources have changed over the years. The Web successfully evolves with all of these changes.
Table reproduced from the Fielding thesis.
“The central feature that distinguishes the REST architectural style from other network-based styles is its emphasis on a uniform interface between components. By applying the software engineering principle of generality to the component interface, the overall system architecture is simplified and the visibility of interactions is improved. Implementations are decoupled from the services they provide, which encourages independent evolvability.”Today let’s work through the different dimensions of REST and how they constrain us to decouple logically independent components of an API. From the decoupling we’ll gain the stability of one component, while another evolves.Decoupling decisions are all about engineering trade-offs. The Fielding thesis defines the tradeoff strategy.”The trade-off, though, is that a uniform interface degrades efficiency, since information is transferred in a standardized form rather than one which is specific to an application's needs. The REST interface is designed to be efficient for large-grain hypermedia data transfer, optimizing for the common case of the Web, but resulting in an interface that is not optimal for other forms of architectural interaction. … In order to obtain a uniform interface, multiple architectural constraints are needed to guide the behavior of components.”We’ve already mentioned how the uniform interface constrains us to decouple state representation from resource identity.
Lucidchart supports floor plan diagrams, and Phil uses it to design the layout of the new home we gave him. Later on Phil decides to swap his living and dining rooms, buys a new TV, and converts the spare bedroom into an office. Lucidchart actually persists the full undo/redo state of a document. If Phil wanted to review his floor plan history, Lucidchart could actually offer two distinct resources, one for each of version of his floor plans, with URIs for each.Because we decouple representation of a state from it’s identity, URIs represent different resources, rather than different representations.
Moving on to evolving URIs we’ll look at uniform identification of resources and using hypermedia.
Lucidchart integrates with several other popular tools, and some features broke briefly a few weeks ago when a partner made two changes to the URLs for their web services. First, they required us as a client to code into our product their URL recipes, and one of those recipes changes. Second, they had a rebranding of their tools and URL paths changed to use the new brand name. They gave us one business day’s notice and we all spent some long hours and late nights scrambling. During the deployment on Sunday night I spent the evening in front of my machine hitting F5 on my browser and watching for the update to happen on their side. When I saw the change come through I quickly ran the deploy command so Lucidchart.com would use the new URLs. There were hiccups in the process causing the systems to remain out of sync for a day and one integration feature didn’t work until the next evening. It was a mess.We’ll walk through two design strategies to handle evolving URLs.Phil uses Lucidchart to share his floor plan, and uploads a photo of the finished look to go with it. After a few weeks, Lucidchart engineers move images so they’re served from images.lucidchart.com, which is actually a CDN. The Lucidchart document service used integers as IDs in the web service interface.
The second design strategy for evolving URLs is to use the uniform interface’s status codes.At a minimum use 404 NOT FOUNDMany people are familiar with the concept. I mention it here because few of us actually use the powerful design solution. I’ll also show a sample of how to do this in Play.
Play provides robust support for generating hypermedia, complex routing from URIs to implementations, and use of uniform interface features like redirects.Here’s a quick sample of how simple it is to generate hypermedia using Scala & the Play Framework. The getUser method asks the Play framework’s routes library to calculate the URI to the user’s account page. It then passes the URI into the user template to generate the hypermedia response. Then it sends the hypermedia back to the user.
The redirectImages method on the left performs a simple redirect to the new image service, and the route definition is on the right.
The HTTP 1.1 RFC (# 2616) defines 47 different header fields that describe as much as possible about the request. Additional RFCs extend the set of standard header fields, and the set is further extensible by any application.Roy Fielding listed some benefits of self-descriptive messages as:“REST's client-server separation of concerns simplifies component implementation, reduces the complexity of connector semantics, improves the effectiveness of performance tuning, and increases the scalability of pure server components. Layered system constraints allow intermediaries--proxies, gateways, and firewalls--to be introduced… without changing the interfaces …. REST enables intermediate processing by constraining messages to be self-descriptive: interaction is stateless between requests, standard methods and media types are used to indicate semantics and exchange information, and responses explicitly indicate cacheability.” – Fielding Thesis 5.3.2This request contains instructions from the client to the server on…
This response contains instructions and metadata about.If we include as much descriptive information as possible in the messages, then the system can evolve without server developers delivering out-of-band communications to client developers, and without out-of-band updates to the clients.Think of the following types of changes that the server can make without developers providing out-of-band notifications and updates to the clients:connection timeouts, content types, content encodings, the character set, checksums, proxies, firewalls, gateways, warning messages, referrers, cross-origin resource sharing, cross-frame display options, URLs, delayed redirection, response status.Because it is a uniform interface, clients need little more than widely available HTTP libraries and the RFCs to handle all of those metadata and control instructions.A second evolvability benefit is the ability to adopt mature designs. It’s just in our natures as engineers to prefer our own designs over others’. I always seem to regret doing that when it comes to the uniform HTTP interface. It has already passed through multiple iterations by people way smarter than myself and is proven to be highly effective at building available, performant, and evolvable applications.
Actual example from Lucidchart.There is no way to specify the actual content-type or encoding. It’s pretty obvious the encoding is base64, but is it a jpeg, png, bmp, tiff, something else? The status code, ID format, and entire response content-type are all homegrown and subject to change. Whereas how could the second one ever break?The second service uses the standard response code, the standard ID (URI), the standard Content-Type specifier, and the standard media format. What in the world could possibly change in a backwards compatible way?!
“I am getting frustrated by the number of people calling any HTTP-based interface a REST API. Today’s example is the SocialSite REST API. That is RPC. It screams RPC. There is so much coupling on display that it should be given an X rating.”http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-drivenWe’re talking about evolvability, but let’s address the immediate objection that arises to the RESTful approach: performance.Quick note on performance of the cohesive approach:Usually there’s a hidden assumption that requests will be run serially, one at a time. One of the performance benefits of the loosely coupled solution is that you can parallelize the heck out of it.Well, Derrick, don’t you know that modern browser only allow 6-8 outstanding HTTP requests at a time?1. I took a service like this once and found that it was the most frequently requested resource and took 300 something ms. After refactoring it to be more cohesive we reduced the response time to about a quarter of the time.
By far, the most effective solution to a stable API is to use the uniform interface, including standard media type. But, if we decide to invent a new interface, how do we version it?The URL, right?Well, what is our goal? Our goal is to decouple resource identity from representation, so we can evolve the representation independently. What have we done here? The opposite!We’ve all seen this approach, and I think the reason it happens is simply that the URL is the piece of the HTTP application protocol that people are familiar with manipulating.Let’s consider a more effective solution.
This example is taken from Peter Williams’ blog barelyenough.org (barelyenough.org/blog/2008/05/versioning-rest-web-services).