Optimizing the web application interface between consumers and producers

Anthony
Slalom Build
Published in
13 min readMay 26, 2016

by Sean Owiecki, Anthony Lee, & Westin Wrzesinski

Introduction

Many modern web applications have embraced the tenets of REST to obtain clean separation between resources, take advantage of HTTP features such as caching and verbs, and to describe discoverable relationships between resources.

Ideally it’d be as simple as defining a REST interface that could be used by multiple consumers targeting any number of resource producers. The underlying datasources could be shared or siloed depending on the service architecture.

In practice however full adherence to the REST tenets and creating a perfect implementation is hard to achieve. Most complex applications face some degree of compromises and pitfalls that result in the interface being classified in more of a “RESTful” category.

One example would be missing hypermedia usage to reference resources. This opens up the possibility that resources might duplicate and tweak the representation of a resource. As a consumer it makes it hard to intuit the interface and producers to create consistent documentation for the interface.

Additionally an over adherence to REST may result in an interface that requires suboptimal data fetching patterns. This includes a resource that may be too fat or too thin for the consumer’s needs which can lead to specialized endpoints to contain exactly what a specific consumer would need. Having smaller resource representations also results in making an abundance of network calls to fetch data for a content rich page or to determine complex UI interactions.

Having better processes and review standards would definitely help address some of these difficulties. However, is there a different way to think about the interface to both optimize and increase consistency in the way we interact between consumers and producers?

Web applications have exploded in the numbers of users and output of content. This is a real concern for many applications that need to be performant and highly interactive. Facebook and Netflix are two such applications that have been leading the forefront in trying to solve this at scale, utilizing GraphQL and Falcor respectively. They have both introduced new concepts that address some of the difficulties in implementing and using REST, to be more flexible and efficient with the consumer and producer interaction.

GraphQL

What is GraphQL?

GraphQL is a new query language specification to fetch and mutate data that takes into account that your data may be nested, and that your data will have complex relationships. GraphQL does not make any assumptions about what your persistence layer may look like so that you may use whatever systems you already have in place or feel makes sense with the data storage needs of your application. Most commonly we are seeing usage with NodeJS and MongoDB so the examples in this article will highlight that use case.

Image source here

Interacting with GraphQL as a Consumer

Query Examples

GraphQL has an expressive query language available to the client to request data. Rather than having multiple endpoints based on the entities the client is querying GraphQL will expose a single endpoint such as /data and the client will supply it’s queries to.

The query language has a few parts to it but let’s start with the simplest of examples: an employee database where we just want to get employee names.

Get Employees Names

Response Employee Names

With that example we used a root query field called employees and asked the GraphQL middleware to give us just the name field from those employee data objects. But GraphQL is a lot more expressive than that. Remember that GraphQL is hierarchical so we can do queries such as getting us the employee names but also the names and members names of projects that each employee is staffed on.

Get Nested Company Info

Response Nested Company Info

One could imagine a REST API with an endpoint listed below. Using a GET request at that endpoint most likely means the consumer would be receiving the entire employee object in the queries. Many REST APIs require a special header, query param, or use an ad hoc endpoint as an attempt to limit the data coming back to be a certain subset. This means that the consumer will have little control and the API designer will have to specify very tersely what the subsets vs full sets of the Employee data look like.

  • GET /employees
  • Headers: Prefer: return=compact;
  • Query Params: compact=true | false

Query Arguments

Queries can also contain arguments defined by the GraphQL schema. Those arguments can do almost anything the author of the schema desires. Some simple examples are limits, ids, or sort orders.

Let’s look back at our Employees data, and now pretend we have more than one employee. We can do queries such as

Get Employee by ID

Response Employee by ID

This isn’t too much different than how a REST API might behave as you can think of these arguments like query parameters. For example you might have the endpoints listed below in a typical REST API. But the API maintainer needs to ensure that the compact mode listed in the last example is also wired into this endpoint so the consumer can still request a compact form of the employee data if needed. It’s also important to note that GraphQL allows introspection on the queries you can perform so the REST API will need to have documentation so that the consumer knows about the headers, query params, and endpoints available to get certain employees, sort them, and etc.

  • GET /employees/:id
  • Headers: Prefer: return=compact;
  • Query Params:
  • compact=true | false
  • GET /employees
  • Headers: Prefer: return=compact;
  • Query Params:
  • compact=true | false

GraphQL Design Highlights

Facebook has their open source implementation of GraphQL in JavaScript located here, but many others have ported it to various languages.

GraphQL focuses on providing a hierarchical query system that is shaped like the response data. This allows Consumers to request exactly what they want and allows some flexibility without requiring changes on Producers.

Introspection

As seen in the above examples GraphQL seems nice in the way you can supply query arguments and desired fields, but the question is how does the consumer know what is available. There is a lot of power in introspection to make life a lot easier for anyone or anything that wants to interact with GraphQL.

First lets take a look at the example above where the consumer could request an employee by ID. How would the consumer find out about the argument name and availability of that feature in GraphQL?

Get Collection Introspection

Response Collection Introspection

These simple introspection queries come for free with GraphQL. It can be expanded on with deprecation notices, descriptions, and more. Some of these fields such as description require the developer to maintain but the above query for arguments and types are examples of a few free out of the box introspections. These introspections make documentation dead simple to keep up to date and available. And also because you can query for the documentation from the GraphQL endpoint there is no need for a separate page or application to maintain in order to display documentation information. There are a lot of great documentation tools you can use with REST APIs, like Swagger, but it is hard to compete with an almost entirely self documenting system when it comes to ease of maintenance.

Data Mutations

Mutations provide a way for the client to create, update, and destroy data. Mutations occur one after the other. Because they occur in sequence it allows errors to be detected and propagated very easily. Mutations can be achieved in REST via PUT and POST methods of course but there is a reliance on knowing which verbs to use based on the API implementations (some use these verbs differently). With GraphQL you are just firing the POST request to the same /data endpoint in the above examples (you can of course rename that). There is less reliance on making sure the proper verb is used as the GraphQL schema manages creations and updates.

Mutations unlike queries have names and parameter lists. First and foremost here is an example mutation that will add a new employee.

Post Add Employee

Response Add Employee

Fragments

Fragments are nice way in GraphQL to group commonly used fields for queries. This allows the client to get away with a lot fewer key strokes. Fragments are a great way to get consistent data sets back when querying multiple similar types in GraphQL. Because the consumer specifies the queries the backend maintainer has an easier time designing the API as he doesn’t have to keep all of these domain models and endpoints in sync with what they return (compact vs full set) like in a more traditional REST API.

To illustrate fragments let’s first show an example of how we can save queries to variables.

Employee Query

You can imagine times when it might be useful to save the result of queries into variables, but in this example the repetition of fields was annoying. This is where fragments can come in. Fragments must specify the type the are working on and then the fragments are supplied to each query required.

Employee Fragment

Fragments can contain nested fields, compose other fragments, or be mixed with fields at the query level. So just a contrived example of using those properties might be

Company Info Fragment

Testing some Queries and Mutations

I would recommend trying out Facebook’s GraphiQL tool which has a sandbox mode with a very convenient autocomplete feature.

Also here is a great repo showing a small example as well as a link to a tutorial on how to build it.

Closing Remarks

GraphQL seems to be a very promising alternative to RESTful API’s. Giving the consumer so much power in regards to data formatting would be very difficult in REST. GraphQL’s hierarchical nature also plays very nicely into the Composition style architecture that most modern web engineers are embracing. However, the setup of producers for GraphQL does take a decent amount of effort and more advanced use cases, such as granular authorization patterns, may be more difficult to set up before GraphQL fully matures.

Pros

  • Hierarchical data is very pleasant to query
  • From a client perspective the data is very easy to interact with and request
  • Introspection is a life saver
  • Fairly self documenting

Cons

Most of my usage of GraphQL comes from the JavaScript implementation, but below are a few pain points I have found using GraphQL.

  • A LOT of boilerplate and setup to get up and running.
  • Moving very fast. Lots of changes to the JS implementation.
  • There still seems to be some work in keeping Models in sync with schema when using Mongoose & GraphQL without additional tools like Graffiti

Falcor

What is Falcor?

Falcor is a web interface platform that unifies backend data into a single Virtual JSON model object. Interactions with your traditional services are encapsulated through the Falcor model. The model utilizes a new convention called JSON Graph to describe resource relationships and reduce duplication. This approach strives to reduce the network traffic required to populate most web consumers and better describe interconnected data.

Netflix recently open-sourced Falcor as a “developer preview”.

Interacting with Falcor as a Consumer

Query Examples

Compny Model

Get Employees Names

Get Employees Range

Paths are sequences of keys used to traverse a JSON object from its root. This is similar to object traversal in JavaScript, with a few notable differences. Falcor allows Paths to be described as an array of keys or a string, but note that string paths are parsed into arrays at runtime. Paths can also begin with an indexer for the first key in the path, e.g. `[“employees”][0][“project”]`.

References are paths contained within an object’s properties describing symbolic links to other locations within the JSON object. This is the “magic” that turns the JSON object into a JSON Graph. Let’s take our previous JSON object and add references to turn it into a graph:

Employees and Projects JSON Graph

With these references in place, we can now identify an employee working on the first project by querying `[“projects”, 0, “employees”]`.

One caveat here is that the number of records to retrieve must be explicitly declared.

Similar to GraphQL, we can define special queries and use them to retrieve our data.

Get Employees by ID

Falcor Design Highlights

JSON Graph

JSON Graph is a convention for modeling graph information as a JSON object (source: http://netflix.github.io/falcor/documentation/jsongraph.html)

The JSON Graph is one of the bigger divergences from REST but also where Falcor gets a lot of its power. Instead of tying one endpoint to a single resource type, JSON Graph allows a single JSON object to describe multiple resource types and any relations between them. The name itself emphasizes the design choice to model a graph within a hierarchal format in JSON. This is done by adding new primitive types such as References.

References within a JSON Graph points to another key in the graph by utilizing a Path. The Path is dereferenced as part of resolving the value for a Reference. This lets only one instance of a particular resource to be fully populated within the Graph. The main benefits to this include avoiding multiple declarations of the same resource to not only reduce the JSON size but to avoid having stale versions of the resource.

Unified Falcor Model

Falcor focuses on providing a Unified Falcor model to allow a single endpoint for Consumers to utilize. Similar to GraphQL, this allows the Consumers to have access to the entire data set but request only the parts they want. Typically this would compose different backend systems into a single interface. Producers can optimize fetching data by only requesting from systems that are part of the Consumers’ route. Additionally the unified model becomes the documentation for the API, since it’s the path to interact with the application data.

Good model and route design then becomes a crucial part of balancing interactivity and performance. A guiding principle of model and route design is that Consumer interaction needs to be explicit through bounded interactions. For example, this means that when fetching data it is not possible to do a ‘fetch-all’ type of interaction. This is intentional to force the Consumer to pick the right amount of data for their particular view. It also encourages Producers to bound their downstream interactions to prevent source data growth to adversely affect upstream Consumer performance.

Optimized Interactions

As a Consumer the main optimization is the ability to request all the relevant data in one request, regardless of the number of downstream systems. This is due to a combination of the previous design choices, References and a Unified Model.

In addition to the previous optimizations Falcor includes the ability to cache the Model on the Consumer-side and batch any requests to the Producer. The cache is an in-memory cache of all previously fetched data. The size of this cache is configurable and automatically overwrites the oldest data first once it has reached capacity.

Batching allows the Consumer to specify multiple Paths that should be utilized by the Producer to return in the Unified Model. Subsequent requests for data gets added to the Consumer-side cache.

Closing Remarks

Falcor is an interesting take on optimizing the web interface and is based on Netflix’s evolution of their own REST API. The ideal use case would be a Paged Catalog and Detail View with referential data, where the Consumer can explicitly control how many results to utilize but take advantage of the optimizations provided by JSON Graph.

Pros

  • Reduced network traffic due to lower number of HTTP requests and intelligent caching via JSON graph.
  • Can be used with data-binding (e.g. Angular) or without. (e.g. React, which is used by Netflix).
  • Fairly self documenting.

Cons

  • Requires Routes to be explicitly defined and designed for optimal composability. Numerous Routes may lead to maintenance headaches.
  • Not a query language so things like “SELECT ALL” are not possible. Need to be explicit in the number of records wanted for collection-based resources.
  • Young community due to recently being open-sourced; is not yet in widespread use beyond Netflix.

Conclusion

As you’ve seen GraphQL and Falcor are both attempts to rethink how to optimize the interaction between consumers and producers, taking a different approach to get there.

GraphQL is a powerful query language capable of fine-tuning Consumer interface interactions to provide flexibility and expressiveness. However, this consumer flexibility comes with a large upfront cost to implement on the producer side.

Falcor focuses on the relationships between resources and how best to reduce duplication by utilizing references. The data itself is the Consumer interface and is based on familiar routing and REST concepts. However, it has limited querying abilities and requires explicit data interaction.

Although they are fairly new projects, both GraphQL and Falcor are backed by active communities and are used by two of the biggest leaders in modern web applications. Both deserve a look at the very least but at this stage, given the tooling and maturity of each project, should be judiciously applied to your application and problem domain.

About the Authors

Sean Owiecki is an Engineer with Slalom’s Cross-Market delivery center in Chicago. He is passionate about building clean, maintainable code and fitting as many movie references into feature branch names as is possible.

Anthony Lee is a Solution Principal with Slalom’s Cross-Market delivery center in Chicago, where he helps clients modernize their front-end and back-end architectures.

Westin Wrzesinski is a Solution Architect with Slalom’s Cross-Market delivery center in Chicago. He is involved with architecting full stack JavaScript solutions and training folks on the latest and greatest in the JavaScript landscape. Reach out to him on Twitter @westeeze.

--

--