An Anaplan perspective on GraphQL

blog_engineering_knowGraphQL_featured_700x300

With several new services and capabilities coming soon to the Anaplan platform, multiple engineering teams here have started to look at exposing service capabilities via GraphQL. It’s a different way of composing an API than REST, which is familiar, tried, and true. There must be an acre of pixels spilled about GraphQL so far, but none of them discuss what is pertinent to Anaplan, so let me try to provide that perspective. This is meant to be a bit of an overview for developers who are interested in how they might make use of GraphQL and how to think about it.

What is it?

GraphQL is a data selection and update language that is very well suited to deliver data and interaction to Web and mobile applications. It gives the client control over what it gets from a service in a way that is unavailable in REST. When requesting information from a service, the client provides a terse query, in the form of a string, and gets back the information requested. The philosophy is “ask for exactly what you want, get back exactly what you ask for.” The query looks a lot like an outline for the JSON (or compatible) response the client will expect, so it feels natural to the client developer. While GraphQL is primarily an object traversal language with some light capabilities for data query, it also defines mutation requests and subscriptions (a kind of long-running query). A server that supports GraphQL maintains a schema of the queries, objects, and attributes that it supports, and the client can retrieve information from the defined queries. Instead of many endpoints that each surface a bit of information, a single endpoint surfaces a large variety of information.

Don’t get hung up on the “Graph” part of the name. It’s not really for querying graphs. The name just emphasizes the ability to follow connections from one thing to another.

For example, let’s assume we have a GraphQL service that provides some information about Anaplan workspaces and models. This example will help us get a taste of what a request and response would look like. An invocation of a metadata query to get the name and some information on the modules of the model with ID “model123” might look like the following:


query metadata {
  model (id : “model123”) {
    name
    modules {
      id name
    }
}

And the response (in JSON) would look like:


{
  {
    “name”: “Sales Operations Model”,
    “modules” : [
      { id : “model123/10200000001”,
        name: “Revenue Sources”
      }, 
      { id : “model123/10200000002”,
        name: “COGS Calculations”
      }
    ]
  }
}

In the above, model is a field that takes an id argument and results in a model object, and from that object we are asking for its name field and its modules field; furthermore, we are asking for the id and name fields from the modules. This is like an outline provided by the client for the information the service gives back. (We’ll explore that idea some more in a bit.)

While GraphQL is a kind of query language, it completely lacks support for functions and functional composition on the query side. For example, there isn’t a GraphQL way of saying “where creation date is more than 30 days ago” or “sorted by age.” Compared to REST, it is much more expressive and more concise than the OData variation of REST for the equivalent capabilities. (OData is more expressive in things like “where creation date is more than 30 days ago.”) And while GraphQL is sometimes described as a shaping language, that is a bit misleading. You can request information that follows the shape provided by a schema, but you cannot reshape the data at all.

GraphQL provides a type system with classes, interfaces, and unions, among other constructs. The type system is currently somewhat light but could easily grow complex with further spec changes. There is an inbuilt metadata model that a service can expose in special GraphQL classes and schemas, which allows interesting and useful tooling to be built. There is a significant and growing ecosystem of these now.

GraphQL is opinionated toward the needs of graphical client interfaces. However, it implies no particular transport API and is suitable for clients that are not simply GUIs. You can use it over HTTPS, or you could use it over WebSockets, raw sockets, AMQP channels, etc. Although having GraphQL return results as JSON is currently popular, you could return MessagePack, YAML, XML, or other text and binary formats.

GraphQL is owned by Facebook and therefore subject to arbitrary changes (though they will probably be thoughtful). There is currently an open-source governance model for the specification with a fairly active community. The spec is kept updated on Facebook’s GraphQL GitHub repository. The first runtime was built by Facebook, but there are several available now. You can roll your own whenever you want, as the source of truth is the language specification and not any particular implementation. (Though as we publish this, there is a question around Facebook’s patent for similar languages.)

Anaplan’s first implementation of GraphQL was homegrown, as there were no plausible external implementations to look at; however, we are now also using the Node.js implementation and are evaluating whether other libraries can support it in a way that meets our needs, too.

Why would we use it?

A client can make a single request for exactly the information it wants, including entire families of related objects, and get a predictable result in a form that is easy for it to handle. The client is in much more control than with REST, can make fewer requests to get what it needs, and will spend much less code and resources on handling the information it gets back. Mobile clients benefit from reduced round-trip chatter and no unnecessary data.

For example (going back to our simplified example), to get the workspaces associated with a customer, if you are only interested in the workspace IDs and names (and not any of the other attributes of each workspace), you could ask:


query metadata {
  workspaces (customerId: “abc123”) { id name }
}

In this case, you would get back:


[
  {
    “id”: “ws1000”,
    “name”: “development models"
  }, {
    “id”: “ws1001”,
    “name”: “production models"
  }
]

If you wanted workspaces with their total size and also their models, you could ask:


query metadata {
  workspaces (customerId: “abc123”) {
    id name totalSize
    models { id name }
  }
}

Then, you would get back:


[
  {
    “id”: “ws1000”,
    “name”: “development models”,
    “totalSize”: 3000,
    “models”: [
      {
        “id”: “model-1010”,
        “name”: “HR Planning"
      },
      {
        “id”: “model-1011”,
        “name”: “Sales Comp Planning"
      },
    ]
  }, {
    “id”: “ws1001”,
    “name”: “production models”,
    “totalSize”: 8000,
    “models”: [
      {
        “id”: “model-2010”,
        “name”: “HR Planning"
      }
    ]
  }
]

As you can see, while the service controls what is available, the client can choose from what is available in a precise way.

Why would this be better than REST? In classic REST, we would need several round-trips instead of one, and if any other information on workspaces or models was returned as part of their objects, then we would waste resources marshaling the data, transmitting it to the client, and unmarshaling it on the other end. Getting the data would require first a query for the workspaces, and then a request for the models for each workspace. If you have four workspaces, that’s a total of five round-trips, and if you have twenty workspaces, that’s twenty-one trips.

Without diving into details, we have one use case where we need to retrieve different permutations of model information depending on what the UI is doing, and these different permutations include different collections of related information. A strict REST model for the information would lead us to build something on the spectrum from dozens of canonical REST calls to bring the information to the client, some application-specific query string formats to use a smaller number of REST calls, to a single call with a very large JSON result from which most of the internal aspects are often ignored. By using GraphQL, we kept the service from being application-specific and kept the client-server interaction lean.

So far, a number of Anaplan services are supporting it, planning to support it, or kicking the tires for certain use cases. In some form, both theAnaplan Business Map and our DocuSign integration use GraphQL.

Note that an expanding set of client libraries and frameworks integrate with GraphQL. The original was Relay, but Apollo also provides components that appear to be well received.

How does a client use it?

To use GraphQL, you send a request formatted as text and get a response to that request. (Unless otherwise stated, I’m going to assume we are using HTTP(S) to send GraphQL requests and get responses.)

It’s intended to return data. Another endpoint is still required to serve up UI artifacts for a Web client.

In addition to the raw GraphQL request and the raw response, the spec includes provisioning for a request to include values for named variables so that the GraphQL query can refer to information without being rewritten every time (like parameters for a SQL request). The response can also contain an errors section to provide information on parts of the request that had some problem being resolved. (There is an extensions section in the response to allow other arbitrary information to be returned.)

Generally speaking, it’s easiest to send the request as a POST. While this may offend a REST purist, it’s simply practical: We can send an arbitrarily long request in a POST body in unencoded text, as opposed to URL-encoding it in a GET. Since you can send multiple create/update/delete/start/stop mutations in a single POST, you can’t clearly separate DELETE vs. PUT vs. PATCH anyway. There is no need to choose an HTTP verb when communicating to the server because the verb is in the request itself.

What does GraphQL mean for our services?

There are a number of implications for the services that expose GraphQL:

  • There will tend to be fewer endpoints per service, and they can announce new versions less frequently. A single endpoint can service an open-ended set of requests and mutations, and new ones can be added at any time. So long as a new version only adds things that were not there before, a client that knows that the new things are there can ask for them, while an older client just won’t be aware of them. This is a change over the conventions of REST, since if a client were just absorbing entire objects it might have built assumptions around object contents that it had no interest in; in this case, the client would need to be alerted every time those assumptions might have changed.
  • Services may need to account for somewhat greater resources used per request. Especially when nested object relationships are exposed, a query may access more information in one request than any REST request would have. Some intelligence in constructing join-paths or other query-plan artifacts may be necessary.
  • We in Anaplan engineering may move through a period of experimentation across services to a more or less standard kit of library support and practices across services. It is easily the case that there is no library out there that is particularly good for our use cases. The details of any given service might be different enough from others to warrant a bespoke implementation. However, data and requests are part of Anaplan’s mission, so building a good framework, if necessary, is a core competency here.
  • Services may need to implement some additional behaviors to support particular clients. For example, Facebook Relay requires some additional things to be implemented within GraphQL to work.

What are the quirks and restrictions?

GraphQL frequently generates great enthusiasm among developers. Before or while leaping into adopting it, take a good look at what it doesn’t do as well as what it does. There are a few quirks that you should keep in mind:

  • Trees and graphs are only indirectly supported.
  • Free-form JSON and other map structures can’t be directly returned.
  • Cursors are not part of the language.
  • Expressiveness requires “language-in-a-language.”

Trees and graphs are only indirectly supported

Suppose that in your service there is an object tree that looks like the following in JSON:


x : {
  id: A1
  x : {
    id: A12
    x : {
      id: A123 
} } }

If you request { x { id x {id} } } you will miss the innermost x with an ID of A123. There is no way to just retrieve the tree starting at the x with ID A1. You need to know how deep you will go.

This fits a particular opinion for clients. A client can’t get surprised with too much information; if you were going to show the information to a user, the client would just ask for some finite set of steps as it gets ready to cache or show them. It also prevents a server from getting lost in some circle of references. However, it also prevents the server from giving you a well-formed tree in one shot.

We can alter the descriptions so that the GraphQL query returns a list of steps and a list of connections. Suppose the service alters its schema to produce a graph that consists of a collection of things and connections between them. A request like:


{ 
  things : { id } 
  edges : { from to } 
} 

can return:


{
  [ {id: A1}, {id: A12}, {id: A123} ],
  [ {from: A1, to: A12}, { from: A12, to: A123} ]
}

However, the client now has to apply logic to reconnect these in the right way. If our data was a graph that was more complex than a tree, we were going to do this anyway, so it’s sometimes just right. (For example, the Anaplan Business Map represents a graph of models and their connections.)

You might ask, “Why can’t we just return the tree as a JSON object?” That’s a great question. Read on for the answer.

Free-form JSON and other map structures can’t be directly returned

Free-form JSON and the like just aren’t part of the language. It’s a frequent topic on the specification discussion list. A request result that is a scalar (a string, a number, or a null) can be directly returned. Objects need to be broken down into their most-detailed scalars. This means that to request a complex object, you need a complex request.

There are two solutions to the problem. One is to turn the free-form JSON into a string and return it. GraphQL doesn’t care what is inside a string. The client needs to read the string and decode it as JSON. Doing so is not entirely natural; furthermore, it is likely to have some resource bloatage in serializing and deserializing. But you can do it. Another way would be to color outside the lines and declare in your own implementation that a JSON blob is a scalar, then make sure that any value of it is serialized to JSON within the context of the overall JSON being serialized as a result. (If you’re returning a different serialized form, adjust your recipe accordingly.) However, you’ve just left the standard.

Cursors are not part of the language

A client may only want to get a certain number of entries from some list. Let’s contrast GraphQL’s behavior with that of SQL. In JDBC, ODBC, and other relational APIs, you get a cursor back from a query and consume from it as needed. In most SQLs, if you want a similar effect in the query, you can append a LIMIT to the query to get that many records, and maybe a starting record number to get a particular page of records. In either case, your paging was separated from the logic of the query. Though the GraphQL language provides annotations (which all begin with the @ character) as a vehicle to influence execution aspects—and these annotations could be used this way—there is no language support. Instead, clients (including Facebook’s Relay) require that paging and cursoring concepts be implemented as GraphQL objects and attributes, and this strongly influences any standard handling.

Expressiveness requires “language-in-a-language”

As we noted before, GraphQL specifically avoids embedding any notions of expressions for concepts like sorting and filtering. This should be seen as a virtue, as it would drag in some additional syntax, a great deal of complication in the type system, requirements for naming things, etc. It eliminates a class of issues like language injection attacks, and it also helps make responsiveness more predictable, as clients can’t do completely arbitrary things. Lack of expressiveness is meant not only to support UI clients, but also a particular style of UI client.

A number of Anaplan’s use cases would benefit from expressiveness in one form or another. So we just need to find a way to create the expressions. There are two ways within GraphQL to add sort/filter/build expressiveness like this and pass them as an argument to a field:

  • Create a string that holds the expression, or
  • Create a JSON object that holds some relationship of objects, like a parse tree.

Neither of these is super-satisfactory. Either way, you have to parse these aspects separately from the main request. Parsing a string at least lets you use standard tech like ANTLR or Yacc, and the string has a chance of being intelligible to a human. Parsing a JSON object tree means traversing a tree of objects that the GraphQL parser left you with and converting them into a useful form. Unfortunately, making a JSON form and a good parser for it is pretty unsatisfying: JSON arrays are easy to create on the parsing side but are not easy to work with on the client side. And mapping an expression tree to JSON objects is not very friendly beyond a very limited grammar. That said, there are projects out there that find these methods adequate for their needs.

Wrapping Up

Having read this far, you should now have a feeling for what GraphQL provides to a developer and why it is interesting for our use cases. Flexibility and performance are compelling reasons for our engineering team to consider GraphQL. We have found a number of important engineering aspects related to the use of GraphQL, and in our next GraphQL-related blog post, we’ll dive into some of them.

Related Posts

Blog Sign up

Facebook
Google+
Twitter
LinkedIn
Pinterest