Moving Beyond REST with GraphQL


11 Jul 2019  Sergio Martin Rubio  14 mins read.

GraphQL is a query language that matches up with Domain Driven Design, so you can use your existing entities or models in your GraphQL design.

This query language was created by Facebook and open sourced later in 2015, and since then it has been maitained by the community.

To start using GraphQL you will have to learn a new specification, because it is not a simple implementation, however it is pretty simple and if you are familiar with other query languages it will take you a few hours to understand how it works. Also the GraphQL spec is very well documented and shows you how to use operations like queries or mutations, define schemas or what good practicies you should follow.

How it works

This API Query Language allows you to retrieve data from a service in one go. How can you do that? A single endpoint is exposed by GraphQL, and given a schema which contains your models and operations, you can make HTTP requests to /graphql by providing operation names, a payload and variables.

GraphQL supports both, GET and POST HTTP methods. In case of GET, we have to use a query parameter (?query={operationName{field}}). On the other hand, we could do a standard POST request with a JSON payload.

e.g.

{
  "query": "...",
  "operationName": "...",
}

Remember that URLs can only be sent over the internet using the ASCII character-set.

Operations

Query

Queries are used to fetch data, and you should use them to perform read operations in your service. Here there is an example:

query {
  findHotelById(id: 2) {
      name
      address
      room {
          type
      }
  }
}

The findHotelById is the operation of the query, and everything else inside the operation is called payload. You can use arguments like the one in the previos example (id: 2), that will return the Hotel with id 2.

Queries also support dynamic arguments, and provides a way to pass them as JSON in the operation. You will use them like this:

query MyQuery($hotelId:ID) {
  findHotelById(id:$hotelId) {
    name
    room {
      type
      occupants
    }
  }
}
{
  "hotelId": "1"
}

You could use the shorthand syntax and omit both the query keyword and the query name, but it is a good practice to use these to make our code more readable, and they can be useful for debugging or identify different GraphQL requests.

In case you have to create a complex query you could use Fragments. Fragments are reusable blocks that contain a set of fields. For example:

query MyQuery {
  firstHotel: findHotelById(id:1) {
    ...compareHotels
  }
  secondHotel: findHotelById(id:3) {
    ...compareHotels
  }
}

fragment compareHotels on Hotel {
    name
    room {
      type
      occupants
    }
}

You have to use three dots followed by the fragment name to call a fragment.

Mutation

Mutations are used to alter data and they should trigger insertions, updates or deletions in your database. You can create a mutation by replacing the query with the mutation keyword. This is an example:

mutation {
  newHotel(name:"test 1", address: "test 1"){
    id
  }
}

Just like in queries, if a mutation returns an object type, you can ask for nested fields. In the previous example the mutation creates a new Hotel and returns the id for the created hotel, which was in this case autogenerated.

Apart from sintax, queries and mutations differ from each other on one more thing, query fields are executed in parallel, whereas mutations run sequencially.

Subscription

GraphQL subscriptions are a way to stream data from the server to the clients that are listening. In the same way as queries, subscriptions allow you to ask for a set of fields, but instead of making a stateless HTTP request, a **websocket connection is used to have a stream of data coming from the server, so that every time there is a change on the server, results are sent to the client, or in other words, when a client runs a mutation the subscription is triggered.

“Executing a subscription creates a persistent function on the server that maps an underlying Source Stream to a returned Response Stream.”

/subscriptions is by default the WebSocket subscriptions endpoint. Here’s an example of Javascript client using subscriptions:

function subscribeToHotels() {
    let socket = new WebSocket("ws://localhost:8080/subscriptions");

    socket.onopen = function () {
        let query = `
              subscription MySubscription {
                getNewHotel {
                  name
                  address
                  creationDate
                }
              }
        `;
        let graphqlRequest = {
            query: query,
            variables: {}
        };
        socket.send(JSON.stringify(graphqlRequest));
    };
    socket.onmessage = function (event) {
      // handle response
    }
}

Schema

Schema files are text files with .grapqhl extension. Operations and models are define there, and in order to do that GraphQL provides a schema language which inclues scalar types, markers and other keywords to build complex schemas

Built-in scalar types are:

GraphQL Type Serialized as
Int Signed 32‐bit integer
Float Signed double-precision floating-point value
String UTF‐8 character sequence
Boolean true or false
ID String

Type Markers:

GraphQL Marker Equivalent
<type>! Not Null
[<type>] List
[<type>!] List of Not Null Elements
[<type>]! Not Null list
[<type>!]! Not Null list of Not Null Elements

Here you have an example:

type Hotel {
    id: ID!
    ## Hotel name
    name: String!
    ## Hotel address
    address: String!
    ## Date of the hotel registry creation
    creationDate: String!
    ## List of rooms for a particular hotel
    room: [Room]!
}

You can also add comments to document your schema, and to do so, you just need to add them before each field, type or argument.

Queries, mutations and subscriptions can be created as follows:

## The Root Query for the application
type Query {
    ## Retrieves all hotels
    findAllHotels: [Hotel]
    ## Retrieves a Hotel given an ID (eg: '1, 4, 12')
    findHotelById(id: ID): Hotel
    ## Number of Hotel available
    countHotels: Int
    ## Finds all payment methods
    findAllPayments: [Payment]
}

You just need to define the name of the operation with optional parameters followed by the returned type.

Query, Mutation and Subscription keywords are used as the root of each type of operation for the application. But it is easy to add additional operations by using the extend keyword. e.g.

extend type Query {
    foos: [Foo]!
}

Custom types are also extendible to avoid large list of fields.

There are also other more advance elements like interface, union, enum or scalar.

How to create a GraphQL Spring Boot Server?

We need to:

  1. Define GraphQL Schema.
  2. Decide how the data for a query is fetched.

Define GraphQL Schema

Here there is an example:

There is a Java GraphQL tool library that parses your schema to Java and configure classes for creating a GraphQLSchema.

By default GraphQL tools uses the location pattern **/*.graphqls to scan for GraphQL schemas on the classpath.

<dependency>
    <groupId>com.graphql-java</groupId>
    <artifactId>graphql-java-tools</artifactId>
    <version>5.2.4</version>
</dependency>

Build Spring Boot application

These two libraries are required to start using GraphQL with Spring, and basically there are setting up the servlet:

<properties>
  <kotlin.version>1.3.10</kotlin.version>
</properties>

<dependencies>
    ...
    <dependency>
        <groupId>com.graphql-java</groupId>
        <artifactId>graphql-java</artifactId>
        <version>11.0</version>
    </dependency>
    <dependency>
        <groupId>com.graphql-java</groupId>
        <artifactId>graphql-spring-boot-starter</artifactId>
        <version>5.0.2</version>
    </dependency>
</dependencies>

The first one is the GraphQL Java implementation, while the second one makes available the servlet at /graphql.

graphl-java-tools requires kotlin.version Kotlin 1.3.10, because Spring Boot Starter parent currently overrides it with a 1.2.* version of Kotlin. Spring Boot team has indicated the Kotlin version will be upgraded to 1.3 in Spring Boot 2.2.

For this example MongoDB is used as the repository, since to allow real time updates with Reactive Programming and GraphQL Subscription.

public interface HotelRepository extends ReactiveCrudRepository<Hotel, String> {
    @Tailable
    Flux<Hotel> findWithTailableCursorBy();
}

@Tailable is required to query capped collections in MongoDB. Capped collections will keep the cursor open even after reaching the end of the collection.

The next step is to create resolvers for each object defined in your schema. Query, Mutation and Subscription are root GraphQL objects, and you need to implement GraphQLQueryResolver, GraphQLMutationResolver and GraphQLSubscriptionResolver respectively so that graphql-java-tools will be able to map the GraphQL operations with the methods created in the resolvers. Here there is an example:

@Component
@RequiredArgsConstructor
public class Query implements GraphQLQueryResolver {

    private final HotelRepository hotelRepository;
    private final PaymentRepository paymentRepository;

    public Iterable<Hotel> findAllHotels() {
        return hotelRepository.findAll().toIterable();
    }

    public Optional<Hotel> findHotelById(String id) {
        return hotelRepository.findById(id).blockOptional();
    }

    public Optional<Long> countHotels() {
        return hotelRepository.count().blockOptional();
    }

    public Iterable<Payment> findAllPayments() {
        return paymentRepository.findAll().toIterable();
    }
}

Method name and signature have to match with GraphQL the corresponding operation definition.

Additionally, you might need to create resolvers for nested fields.

e.g.

@Component
@RequiredArgsConstructor
public class HotelResolver implements GraphQLResolver<Hotel> {

    private final RoomRepository roomRepository;

    public Iterable<Room> getRoom(Hotel hotel) {
        return roomRepository.findAllByHotelId(hotel.getId()).toIterable();
    }
}

In the previous example, when hotels are retrieved and rooms might be retrieved as well, therefore a method to retrieve rooms by hotel ID needs to be provided.

Source Code

GraphiQL, a GraphQL client

GraphiQL is a very useful tool to explore your GraphQL schema and make requests. The most simple way to start using Graphiql is by adding to your pom.xml file as a dependency.

<dependency>
    <groupId>com.graphql-java-kickstart</groupId>
    <artifactId>graphiql-spring-boot-starter</artifactId>
    <version>5.7.0</version>
</dependency>

By default you can hit it at /graphiql.

Highlights and Challenges

  • The main benefit of using GraphQL is that you get what you ask for in a single request, whereas with REST we tend to do “overfetching” or “underfetching”.

  • GraphQL can also be simpler and faster, however you may face unpredictable performance when multiple fields are combined.

  • One of the challenges is versioning. You will have to deprecate fields and will not be able to know if a field has changed over time.

  • Another point against GraphQL is caching. In GraphQL, you cannot use URLs as cache identifiers, so you need to create unique keys and implement caching in your application.

  • There is also an extra overhead, since the server needs to do more processing to parse the query and verify parameters.

  • Lastly, in case of a simple API the extra complexity added by GraphQL is not worth.

Conclusion

GraphQL is similar to an API gateway or proxy server that sits in front of your downstream services or data sources, and just like HTTP we can use verbs to get exactly what we ask for. It is also an alternative to REST, SOAP or gRPC, but this does not mean you have to through away your current architecture, for instance you could have GraphQL on top of your REST services.

This technology is becoming more mature and is available for multiple languages, including JavaScript, Python, Ruby, C#, Go, Scala or Java, and companies like Pivotal are heavily supporting GraphQL. In fact it was one of the topics presented in Spring IO 2019.