Defining the API Contract: Part 3

Jesus de Diego
adidoescode
Published in
18 min readFeb 11, 2022
Photo by Alina Grubnyak on Unsplash

Introduction

Following the old good topic of API Contracts and API styles and architectures (you’ll remember our articles about REST and gRPC) we are going to handle a different animal now: GraphQL based APIs. This article is intended to be an introduction to some basic concepts to GraphQL and how easy or difficult is to start with it, but not only. It will all be a space for some thoughts about GraphQL best fits and architectures. We’ll go through some generic use cases and review a simple case for implementation in the most extended Java framework.

So, basically you can take this article as an introduction to GraphQL from the API as a Product /API Contract first approach. There are tons of materials and resources explaining quite well how GraphQL works and how to start building GraphQL based APIs in most of the main programming languages. But this is not the goal of this article. Our focus is, to find out what are the most interesting parts to the API contract and API stakeholders, producers and consumers, and the API lifecycle, and see what the main approaches can be, what issues we can find and what mitigations can be applied when using GraphQL based APIs.

Is GraphQL an API Style?

Well, it is not. It is usually handled as a different API style but (and I’m being very honest with you) I don’t think it is. The GraphQL’s web site says this quite clearly:

They couldn’t be clearer about it

GraphQL looks like a query language for other resources, like databases or APIs, that’s true. But if we are going to use this way of consuming our APIs (using this query language) we are actually exposing our data and interacting with other components in a different way, not following the traditional request/response pattern, not sending commands to a gRPC service. I’d call this way something like Graph-based API Server-in-the-middle of other APIs.

I know, I’m not good with names :P

So, for the sake of clarity, we’ll use the term GraphQL based APIs from now on, not forgetting it is a query language, though.

How does it work?

Let’s see a summary of the main aspects of GraphQL in three points:

1-Graph-based Data Representations

I assume you are familiar with data representations. From a generic point of view, it is the way we define the structure of data entities in a given scope. For instance, you define schemas or data models in the OpenAPI based API contract. Data types, names, enumerations, arrays, nested objects etc. are defined in this way and they become a component part of the definition of the API in the OpenAPI spec. You do the same in AsyncAPI (schemas) and Protobuf specs.

The definition of the data representation as graph schemas is making the difference. While you define a linear hierarchical data model in OpenAPI, AsyncAPI and gRPC, the data representation in GraphQL is (surprisingly) a graph. So, the principle in GraphQL is based on the creation of a representation of data that is designed like a visual graph. You have to use the SDL (Schema Definition Language) to make the schema as the core of any GraphQL based API. It is available in any GraphQL server to define every available functionality for the API consumers.

The graph part of GraphQL describes a data structure of a collection of objects/nodes that are connected to each other through a set of links or edges. Relationships between different objects can be represented in a user interface as a result of this graph structure. The key concept is that the data structure is non-linear, meaning that one object can be connected to more than one other object, and relationships can also be circular.

You can review the file schema.graphql to have a look about how a graph schema looks like. For instance, let’s have a look at the Person node:

The “!” at the end of a field data type means this is a non-nullable type.

Apparently it is not different to the schema definition we can find in OpenAPI or Protobuf. But if we have a look at the file we’ll find the relationships between the different nodes in the graph:

Connections and edges…

It’s out of our scope to describe here how to make a perfect graph schema but we can see the difference. The graph schema is a non-linear definition of entities that are related to each other in different ways. Graphs and trees are the main types of non-linear entities. We have also define vertices and edges. Social networks are based on graphs!

Probably the main difference with the definition of schemas in OpenAPI, AsyncAPI and Protobuf specs is, they are based mostly on linked lists (linear) while graphs offer a chance to define more complex relationships/levels that cannot be traversed in a single run but are (in theory) more efficient. Anyway, there is no need for the graph to be acyclic (cycles are perfectly acceptable). Thus, the client can get from one field to its children, but it can’t automatically get back to the parent unless the schema defines this explicitly.

2-Options in GraphQL based APIs

In short, a GraphQL schema is a textual representation of our application’s data graph and the operations on it. The data graph defines the entities and the relationships between them. To complete the schema, we need to add GraphQL operations. The GraphQL operations provide the information needed to generate and validate operations on the data graph.

Queries

Queries is the traditional way of asking questions to a system, including databases, APIs, etc. SQL queries based on the keyword SELECT are a well known example. Sometimes REST/RPC API designers include a POST endpoint that attaches a query in the body. In pure RPC and Protobuf APIs, specific methods in services accept an object as a question to the API.

GraphQL works in the same way but exclusively based on sending a named query where you define the fields you want to retrieve.

Mutations

Mutation means a change in the data. You can manipulate server-side data via GraphQL using the keyword mutation before a query. We are talking here about something similar to POST (new data), PUT (updates) and DELETE (remove data) if we are going to feel more comfortable with the good old HTTP methods.

You can see an example of both:

Directives

GraphQL offers something called directives.

A directive is an identifier preceded by a @ character, optionally followed by a list of named arguments, which can appear after almost any form of syntax in the GraphQL query or schema languages […] The possible applications of directive syntax are numerous: enforcing access permissions, formatting date strings, auto-generating resolver functions for a particular backend API, marking strings for internationalization, synthesizing globally unique object identifiers, specifying caching behavior, skipping or including or deprecating fields, and just about anything else you can imagine.

So, directives can be provided by the spec or defined by us. Let’s have a look to the built-in ones because they allow us to do something else. In this case, we’ll focus on @defer and @stream directives.

@defer means Asynchronous APIs!

When the GraphQL execution engine encounters the @defer directive, it will fork execution and begin to resolve those fields asynchronously. While the deferred payload is still being prepared, the client can receive and act on the initial payload. This is most useful when the deferred data is large, expensive to load, or not on the critical path for interactivity.

We can define a non-blocking API using the graph schema!

@stream means (Oh, surprise!) Streaming!

Similar to @defer, the @stream directive also allows the client to receive data before the entire result is ready. @stream can be used on list fields.

OK, but what type of streaming? Remember we saw in gRPC APIs we could define 3 different types of streaming connections:

  • Server Streaming (Unidirectional)
  • Client Streaming (Unidirectional)
  • Bidirectional streaming

According to GraphQLs official blog:

While the GraphQL specification doesn’t specify transport protocols, we expect the most common transport for queries with @defer/@stream to be HTTP with chunked transfer encoding. This allows a GraphQL server to keep a standard HTTP connection open, while streaming each payload to the client as soon as it’s ready. It has low overhead, has been supported by browsers for decades, and can work easily with most infrastructure.

So, it looks like the streaming is only server streaming type.

Streaming is based to handle long duration connections to push data to the consumer as soon as each payload is ready. So, the time the connection is blocked depends upon the design of the data and the relying architecture.

3-Composing Queries to the Graph

In GraphQL, the consumer requests data sending always queries specifying the precise sub-set of the schema that is being required. So, each query can require a completely different data structure back. Using again the REST example, in REST, the structure of the request object is predefined on the server (and it is usually constant and uniform across all requests and responses although some implementations can change the structure of the data in the response according to parameters. nevertheless, this is not part of the REST architectural design). In GraphQL, the consumers always define the data structure to be sent back and this is an intrinsic feature of the specification.

Introspection is another awesome feature in GraphQL based APIs.

We designed the type system, so we know what types are available, but if we didn’t, we can ask GraphQL, by querying the __schema field, always available on the root type of a Query. Let’s do so now, and ask what types are available.

By using introspection we can review the details of the graph schema. We can find out what types, queries, mutations, etc are defined in the graph schema using GraphQL. Introspection is key to get visibility of the whole schema and therefore it makes it much easier to work with it for programmatic consumers.

OK, this is the end of the section. What about a recap now, in 100 sec, before hoping to the next one?

Use Cases

In this section we’ll have a brief look at different architectural options and possible use cases where GraphQL can result in a good fit.

1-Aggregation of APIs

This use case is simple. You have a bunch of REST APIs that are consumed separately. However, they are related from the data domain point of view and we have some consumers that want to get aggregated data from this domain in a single query, not getting data from each separate API and then process, aggregate and parse in the consumer side. We obviously want thinner consumers and more simplicity in the consumer side. This can save a lot of money and time.

The plan is to use the GraphQL engine to act as an intermediate component. REST APIs can keep on being consumed as usual. The GraphQL engine is just another consumer that will aggregate data from the REST APIs to be consumer by other consumers. We can consider it is acting as a gateway to the other APIs.

Imagine a Micro Service Architecture (MSA) with multiple components, each of them exposes a REST API and uses a different database/database schema. The GraphQL engine will act as the gateway to these APIs allowing to consume an aggregation of data. This pattern hides the complexity of the underlying MSA (consumers do not care about MSAs) by offering an integral data schema.

The API contract based on the graph schema is the contract to be used by final consumers. However, we have to maintain the contracts for the underlying REST APIs as well. Changes in any of the REST APIs could affect to the graph schema API contract. So, the management overhead increases because of the dependencies.

Swagger2GraphQL is an example of a set of tools that can be helpful to follow this pattern.

2-Aggregation of Resources

This is quite similar to the pattern above but using data resources instead of REST APIs. In this case the GraphQL engine has to be able to connect to heterogeneous data sources and the complexity is quite bigger. It requires as well a big effort regarding the making of the aggregated schema. The need of a data consumer for this aggregation has to be really worthy because the involved cost. In summary, this is a more complex scenario than the previous one.

3-Avoid the Over-fetching and (optionally) get the streams

In this use case we have a REST API with a huge data schema. There are tens and tens of data objects and types, covering a whole data domain. However, some API consumers (e.g. UI API consumers) do not need all this stuff but they get all in every request even though they use less than 25% of the retrieved information. This is not good in terms of usage of resources, latency, clarity of schemas and maintenance overhead.

This pattern uses GraphQL to create a graph schema with a subset of the data domain in the REST API. These consumers will use the GraphQL based new API to fetch ONLY the information they need.

4-Access to Datasets

As mentioned before, GraphQL can be used as an aggregator of different data sources. Datasets can be one of these sources, meaning that we can interact with them by using some kind of interface. Of course datasets per se are not accessible nor usable, they are not running software like databases. They have to be managed by using some managed service, tool, etc. AWS S3 and Azure Storage are examples of managed services used to store, manage, and use datasets.

This approach requires some extra work to write (using GraphQL clients like Apollo or Altair)the resolvers to fetch the data in queries and the implementation of mutations. The good part is, these operations can be described in a graph schema, implemented with the help of a GraphQL client to interact seamlessly with a data resource like S3.

What are the advantages? Well, you are actually exposing your datasets in the buckets to API consumers. They can use the defined graph schema that can follow the dataset definition and send queries or mutations to the API. The GraphQL queries will allow the consumers to fetch the data they want, not only the whole structure or nothing. This is probably an excellent example of usage of this type of APIs.

As an example of usage of a GraphQL API with datasets, AWS AppSync is a good example using GraphQL API to interact with datasets.

Potential Issues

We are going to review some potential problems where GraphQL is not recommendable (by this author). Besides, we have to be careful with GraphQL. To get the biggest potential from this pattern we have to know the pros and cons.

  • Limited amount of tooling. This was true in the beginning but right now we could find many good tools to cover the different phases of a GraphQL API. Design, mocking, testing, implementation, etc.
  • Need to define mutations, queries and resolvers. In the end it is not quite different to the definition of an API contract using OpenAPI or AsyncAPI. The extra overhead is quite low.
  • Error handling is not good. For instance in REST APIs, checking the response status is the only way to know if the request was executed successfully, if there was a server error, or if the resource was not found, your user was not authorized, whatever. In GraphQL, you get something similar to the following when an error happens:
  • Dependencies in some architectural patterns. We saw how a GraphQL server could be used to aggregate other APIs or data sources. This approach is inevitably built on the top of them in a very tight way. Maintenance and testing can be more complicated in these scenarios.
  • Extra consumption of resources. Putting GraphQL engine in the middle will be more expensive in terms of cloud resources.
  • Latency. Again, putting GraphQL engine in the middle will be more expensive in terms of latency. the other APIs and or sources have to be accessed, data has to be obtained, aggregated and sent. So, extra latency is inevitable. This might be argued given multiple network requests to REST APIs (and they are not concurrent) can also take a lot of time. But we have to consider, a few big queries can bring your server down to its knees.
  • Proxy Caching. The graph schema defines queries but queries can get an unexpected subset of available elements. So, caching can be a difficult task.
  • Security. GraphQL can be more difficult to be designed in a secure way. You can have a look to this resource to know more.
  • Learning Curve. REST principles and HTTP protocol are already a common part of developers skills. GraphQL isn’t. Some extra learning and practice might be needed before starting a new project based on GraphQL.

Mitigations

Fortunately, many mitigations to the issues mentioned above are available when using GraphQL based APIs and using Kong API Gateway. For instance,we can handle easily common issues in this kind of APIs using specific plugins, releasing developers of extra implementation (remember the main principle, micro-services are mainly focused on functional requirements while the API Gateway is fully focused on managing traffic and non-functional requirements). GraphQL based APIs drawbacks are usually caching and rate limiting. How to address these issues with Kong? Let’s have a look:

GraphQL Proxy Caching Advanced Plugin. This feature enables cache of response entities based on the provided configuration, which is quite flexible.

GraphQL Rate Limiting Advanced Plugin. This feature allows to use rate limiting to your GraphQL-based APIs consumers.

Regarding security, by using Kong API Gateway it’s possible to apply a complete API security model based on OIDC based integration integrated with ID providers, using API keys, WAF, etc. The API Gateway features and WAF are key to ensure the GraphQL based APIs are protected against the infamous OWASP API Top Ten vulnerabilities.

The GraphQL API Contract

Finally we’ll say something about GraphQL-based API contracts. The main motivation to write this article was about how we can ensure a good solid API contract using the graph schema so that API consumers can be able to send GraphQL queries to our APIs.

Anyway, we are going to expose some basic principles to keep in mind when we are going to create a new API contract based on a graph schema or schemas:

1-The Data Representations in the Graph are the API Contract

Well, this is the base idea in this article, to define the managed data requirements of our application in a graph schema.

The principle would be something like this: The graph schema (that means, the .graphql extension file/s) is the description of what the consumers can actually request from a GraphQL based API. It also defines the queries and mutation functions that the client can use to read and write data from the GraphQL engine/server. In other words, we consider the API Contract is actually described in the graph schema (the .graphql file/s). It is obvious because the graph schema defines the basic elements an API contract has to contain:

  • The data types and relationships we’ll find when using the API.
  • The description about how to use the API (how to get data and how to make changes).

However, remember the graph schema doesn’t have any usable information about where you can connect to the API, what environments are available, what levels of security have been applied, etc. Nothing but the pure description of objects, relationships, queries, mutations. This situation is quite similar to Protobuf files, which basically are a description of types for de/serialization and methods of services. If we want to put together OpenAPI and AsyncAPI based APIs along with Protobuf and graph schema files, we are going to need something else.

2-We Have to Follow Some Guidelines

Because we consider the graph schema as the API contract the contract guidelines follow the same principles applied to the construction of graph schemas. The recommended guidelines are common to all API contracts. Basically, you ensure:

a) Be compliant, be consistent. The definition of the schema entities, fields and data types SHOULD be compliant with the standard version of the Business Terms in the referenced data domain you are working within. This step is mandatory to provide better re-usability to the new API (following the API as a Product principle APIs are reusable assets, not only a piece of running code used by a single consumer in an over-fitted way).

b) Build a clear schema. The schema definition MUST be as clear as possible, avoiding when possible ad-hoc entities, never used relationships nor nested elements and always using the standard terminology from Business Terms in the data domain.

c) Use design tools. They are critical to make possible a wide adoption and save time and resources. You SHOULD use a design tool to compose the graph schema. There are many visualizers and helpers to the edition of graph schemas (e.g. GraphQL Editor). From a minimal point of view, the GraphQL plugin fo VSCode is my preferred option for creation and edition.

VSCode GraphQL Plugin

d) Validate your graph schema. Lint tools are needed to help us to write the right and well-formed graph schema as our API contract. Graphql-eslint is a standard tool coming from the Apollo project.

e) Store the graph schema in the API Registry as the API contract. However, this is a complicated section because most of API Registries (which also are used for design purposes and API contract lifecycle management) don’t support graph schemas. This is the same problem we found handling Protobuf files. Solutions like Apicurio support the storage of all these specs and it can be a kind of solution.

3-Manage the Evolution

Is GraphQL based APIs are versionless like some people say? There are two main approaches

On one hand, The evolution of the graph schema SHOULD be based on the principles of back-compatibility. The API producer can modify the graph schema without modifying the already existing entities and types, for instance by adding new fields. This should allow existing customers to keep on working without interruptions and use new entities in the future. But this approach raises the question about how to manage the lifecycle of the elements in the schema (I mean, versions).

It is possible in GraphQL to deprecate elements or whole areas in the graph schema by using the @deprecate directive. So, you can add a new field and give it a version suffix, then deprecate the old one using the @deprecated directive. Besides, changes in queries, mutations, entities and or types should be carefully planned as usually happens in other types of APIs to be included as part of a specific version. These changes can break existing consumers and provoke an interruption of service. So, the decommission of some elements in the graph, for instance, should happen in a phased, planned way.

You can see some good advice regarding the design of GraphQL schemas here. Absolutely recommendable if you are new to this stuff.

To be used as an example, you can find and have a look at a quite complete example of a graph schema here.

Conclusions

  • The GraphQL way of making APIs is an amazing way of solving requirements in some scenarios but it is highly recommended to have a previous detailed architectural design and analysis to be sure we are not going to be in trouble later on and that REST APIs cannot do the work at 100%. Evaluate your use case and the main architectural patterns. Do not forget the available resources. Sometimes it is better to have many short connections than only the big one.
  • GraphQL has definitely to be considered to provide access to Big Data datasets to be consumed by API consumers. We think this is the use case with the biggest potential.
  • Use directives to go to asynchronous and take advantage of streaming features to make richer and more capable APIs. Your UI API consumers will thank you, for sure.
  • There is certainly a learning curve, not very steep but it can be a blocker in some cases.
  • GraphQL tooling is mature enough and diverse. We can choose tools, they work and they are available to implement the API lifecycle. It’s the same regarding the implementation of the GraphQL engine/server. We have options in the main programming languages and tools to help developers.
  • Most issues we can find in GraphQL based scenarios can be addressed by using the Kong API Gateway functionalities.
  • The API contract in GraphQL based APIs is the graph schema definition (so, the .graphql file or files). The making of them has to be done following strict principles to ensure a clear, usable, published and maintainable API contract for any possible API consumer coming up.

Finally, we strongly encourage you to take advantage of these amazing technologies available for APIs (gRPC, GrapQL) that are consolidated, extensively used and mature. It is possible to implement the API lifecycle using the available tooling for GraphQL based APIs. Remember, the world of APIs is not only about REST. Abandon this uni-dimensional thinking and dare to build richer and better APIs that can doi more and better..

..depending on the use case!

Just to end this up, I’d recommend to join the GraphQL Slack Group to know more and to have a look at the specification to get the details about specific aspects. There are tens of smaller and specialized groups, shared experiences and information that will be very helpful when building GraphQL APIs.

Thanks!

The views, thoughts, and opinions expressed in the text belong solely to the author, and do not represent the opinion, strategy or goals of the author’s employer, organization, committee or any other group or individual.

--

--