GraphQL- Apollo Federation

So far we have talked about the basics of GraphQL and how a simple client-server architecture will work. But when we talk about enterprise-level, we often deal with multiple backend services fulfilling different purposes. A federated GraphQL helps us implement GraphQL in a manner that queries can be executed on multiple backend services. Apollo federation is a set of tools to help us compose multiple GraphQL schemas declaratively into a single data graph.

image source- https://www.apollographql.com/docs/federation/

The image above how Apollo helps club data from different services to be served from a single gateway.

Before getting into details of Federation, let’s take a look at libraries provided by Apollo

  • @apollo/federation provides primitives that your implementing services use to make their individual GraphQL schemas composable.
  • @apollo/gateway enables you to set up an instance of Apollo Server as a gateway that distributes incoming GraphQL operations across one or more implementing services.

And of course, you need Apollo Server for gateway and each of the implementing services we have.

Let us understand some core concepts here

Entities

“In Apollo Federation, an entity is an object type that you define canonically in one implementing service and can then reference and extend in other implementing services. Entities are the core building block of a federated graph.”

Any object can be declared as an entity by adding @key, which defines the primary key for the entity.

type Product @key(fields: "upc") {  upc: String!
  name: String!
  price: Int
}

An entity defined in a service can then be referenced in another service.

type Review {
  product: Product
}

# This is a "stub" of the Product entity (see below)
extend type Product @key(fields: "upc") {
  upc: String! @external
}
  • Note that the “extend” keyword highlights that the entity is implemented somewhere else.
  • The @key directive indicates that Product uses the upc field as its primary key
  • The upc field must be included in the stub because it is part of the specified @key. It also must be annotated with the @external

Resolving

Now the review service needs to have a resolver for product.

{
  Review: {
    product(review) {
      return { __typename: "Product", upc: review.upc };
    }
  }
}

Resolver in review returns representation of product entity. A representation requires only an explicit __typename definition and values for the entity’s primary key fields.

Product service need to define a reference resolver

{
  Product: {
    __resolveReference(reference) {
      return fetchProductByUPC(reference.upc);
    }
  }
}

Extending

While referencing the entity from other services, this service can add fields to the entity. The original service need not be aware of added fields.

extend type Product @key(fields: "upc") {
  upc: String! @external
  reviews: [Review]
}

Whenever a service extends an entity with a new field, it is also responsible for resolving the field.

{
  Product: {
    reviews(product) {
      return fetchReviewsForProduct(product.upc);
    }
  }
}

Further readings: https://www.apollographql.com/docs/federation/

GraphQL- Security

We have covered GraphQL Basics, GraphQL Schema, and GraphQL Architecture. Another important aspect one needs to consider is security. Here we will talk about some of the basic concepts on techniques that can be used to implement GraphQL security.

Timeouts: First and most basic strategy is to implement timeouts. It is easy to implement at the server level and can save one from malformed, complex, and time-consuming queries.

Maximum Query Depth: It is easy for a client to create a complex query with deep relation or at times cyclic relation. One needs to set up a limit on the maximum depth we are supporting.

query{
   me{ #Depth 1
      friend{ #Depth 2
         friend{ #Depth 3
            friend{ #Depth 4
               #this could go on

Complexity: Another way to control query executions to have a complexity limit for queries that can be executed. By default, every query is given a default one complexity.

query {
author(id: "abc") { # complexity: 1
  posts {           # complexity: 1
    title           # complexity: 1
   }
  }
}

The above query will fail if we set the max complexity for the schema to 2. We can also update default complexity for a query, for example, if we feel posts query should have a complexity of 5, we can do that.

Throttling: Another aspect to control clients from overloading the server is throttling. GraphQL normally suggests two types of throttling, server time based and complexity based. In server time-based throttling, each client can be given a limit of time it can use on the server, mostly based on the leaky bucket strategy where time will get added if you are not using the server. The complexity-based throttling poses a limit of maximum complexity that a client can execute, for example, if the limit is 10, and the client sends 4 queries with complexity 3 each, one would be rejected.

GraphQL- Architecture

We have already talked about GraphQL basics and GraphQL schema. As a next step, we will look into GraphQL Architectural patterns to implement a GraphQL based solution.

Before moving ahead, it makes sense that we understand that GraphQL itself is nothing but a specification – http://spec.graphql.org/draft/. One can implement the specification in any language of choice.

Architecture 1: Direct database access

In the first architectural pattern to implement GraphQL, we have a simple GraphQL server setup, which directly accesses the database and returns required data.

GraphQL server with a connected database
image source: https://www.howtographql.com/basics/3-big-picture/

As we can see, this type of implementation is possible mostly for fresh development. When we make a decision while setting up the system that we want to support GraphQL based access, we build the system with first-hand support.

Architecture 2: Support for existing systems

More often, we come across scenarios where we will need to provide support for existing systems, which are usually built with support for REST and microservices-based access to existing data.

GraphQL layer that integrates existing systems
image source: https://www.howtographql.com/basics/3-big-picture/

The pattern above indicates an additional layer between the actual backend implementation and the client. The client makes a call to the GraphQL server, which in turn connects to actual backend services and gets the required data.

Architecture 3: Hybrid Model

We have talked about 2 patterns so far, one where the GraphQL server has direct database access, and the second when the GraphQL server fetches data from an existing legacy system. There can be a use case where partial implementation is done fresh and some data is being fetched from existing APIs. In such a use case, one can implement a hybrid model.

Hybrid approach with connected database and integration of existing system
image source: https://www.howtographql.com/basics/3-big-picture/

Resolver Functions

The discussion about various types of GraphQL implementation is not completed without talking about Resolver functions. A resolver function is responsible for mapping the query with the implementation or actual fetching of data. So all the above-mentioned implementation will drill down to the fact that how the GraphQL resolver function is written to resolve the query and fetch the data.

GraphQL- Schema

In a previous post, I talked about GraphQL basics and how it can help one simplify fetching data for service over REST. Here, we will take the next step and under the concept of schema in GraphQL.

When starting to code a GraphQL backend, the first thing one needs to define is a schema. GraphQL provides us with Schema definition language or SDL which is used to define the schema.

Lets define a simple entity

type Person {
  id: ID!
  name: String!
  age: Int!
}

Here we are defining a simple person Model, which has three fields id, name, which is a string, and age, which is an integer. The “!” mark indicates that this is a required field.

Just defining the Person model does not expose any functionality, to expose the functionality, GrpahQL provides us with three root types, namely, Query, Mutation, and Subscription.

type Query 
{  
   person(id: ID!): Person
}

The query mentioned above explains how a client can send an ID and gets a Person object in return.

Next, we have mutations that can help one implement remaining CRUD operations like create, update and delete.

type Mutation {
  createPerson(name: String!, age: Int!): Person!
  updatePerson(id: ID!, name: String!, age: String!): Person!
  deletePerson(id: ID!): Person!
}

Finally, one can define subscriptions, which will make sure whenever an event like the creation of a new object, updation or deletion happens, the server sends a message to the subscribing client.

type Subscription {
  newPerson: Person!
  updatedPerson: Person!
  deletedPerson: Person!
}

GraphQL- Getting started

GraphQL is a query language for your APIs. It helps us write simple, intuitive, precise queries to fetch data. It eases out the way we communicate with APIs.

Let’s take an example API, say we have a products API. In REST world you would have the API defined like

to get all products details

GET: http://path.to.site/api/products

or

to get single product details

GET: http://path.to.site/api/products/{id}

Now, let’s say Products has schema as

Product

  • id: ID
  • name: String
  • description: String
  • type: String
  • category: String
  • color: String
  • height: Int
  • width: Int
  • weight: Float

Now by default, the REST API will return all the parameters associated with the entity. But say a client is only interested in the name and description of a product. GraphQL helps us write a simple query here.

query 
{
    product(id:5){
         name
         description
    }
}

This will return a response like

{
    "data": { 
        "product": {
           "name": "Red Shirt"
           "description": "Best cotton shirt available"
        }
    }
}

This looks simple, so let’s make it a bit complicated. Say a product is associated with another entity called reviews. This entity manages product reviews by customers.

Reviews

  • id: Id
  • title: String
  • details: String
  • username: String
  • isVerified: Boolean
  • likesCount: Int
  • commentsCount: Int
  • comments: CommentAssociation

Now you can imagine in case of REST API, client needs to call additional API say

http://path.to.site/api/products/{id}/reviews/

With graphQL, we can achieve this with a single query

query 
{
    product(id:5){
         name
         description
         reviews{
             title
             description 
         }
    }
}

This will return a response like

{
    "data": { 
        "product": {
           "name": "Red Shirt",
           "description": "Best cotton shirt available",
           "reviews": [
               {
                    "title": "Good Shirt",
                    "description": "Liked the shirt's color" 
               },
               {
                    "title": "Awesome Shirt",
                    "description": "Got it on sale"
               },
               {
                    "title": "Waste of Money",
                    "description": "Clothing is not good"
               }
           ]  
        }
    }
}

We can see GraphQL solves multiple problems which were there with REST APIs. Two major problems we have evaluated in the examples above

Overfetching: We can explicitly mention all data we need from an API. So even if a Product model has 50 fields, we can specify which 4 fields we need as part of the response.

Multiple calls: Instead of making separate calls to Products and Product reviews, we could make a single query call. So instead of calling 5 different APIs and then clubbing the data on the client side, we can use GraphQL to merge the data into a single query.

Why Graph?

As we can see in the above examples, GraphQL helps us look at data in form of a connected graph. Instead of treating each entity as independent, we recognize the fact that these entities are related, for example, product and product reviews.