Developing GraphQL API with Netflix DGS Framework (Part-I)

While REST is common standard nowadays for developing the API for applications, If you have multiple clients like mobile desktop, web applications requesting different data , Rest API mostly results either under fetching or over fetching the data. We can use the GraphQL frameworks to develop API to resolve over fetching or under fetching data from API.GraphQL clients can decide exactly what data they want to receive in response.

GrpahQL specification was developed by Facebook and open sourced in 2015.

What is GraphQL?

GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools

Comparing GraphQL and Rest API

Advantages

  1. GraphQl works based on one end point, but Rest API requires multiple endpoints( url) for requesting different resources.
  2. GraphQL clients can decide what data they want to receive in given request , Rest API returns all the for each request irrespective of your requirement. Where there is network bandwidth limitations , GraphQL API performs better compared to REST API
  3. GraphQL API is schema based, so developer look at the schema and knows what can be expected for each request.
  4. GraphQL APIs can be evolved without needing the new versions. We can add new fields and types to your GraphQL API without impacting existing queries

Drawbacks

  1. Unless you have multiple applications or clients accessing your data, Rest API might be simpler to implement.
  2. You can cache REST API request easily, but it is not easy to cache the GraphQL queries due to change in parameter
  3. It is easier to understand errors in REST based on the response code. GraphQL uses single HTTP response code( 200) for all the responses including the failed ones. Developer need to parse through messages to determine errors

Let’s develop GraphQL API with Netflix DGS framework

Create a SpringBoot project

The Netflix DGS framework is based on Spring Boot, so first we need to create a Spring Boot project. Go to https://start.spring.io and create a Spring Boot project. The only Spring dependency needed is Spring Web. Download the zip file, extract and import the project into your IDE.

Adding the DGS Framework Dependency

We use the Netflix DGS Framework BOM to add the dependencies

<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>com.netflix.graphql.dgs</groupId>
				<artifactId>graphql-dgs-platform-dependencies</artifactId>
				<!-- The DGS BOM/platform dependency. This is the only place you set 
					version of DGS -->
				<version>4.1.0</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>com.netflix.graphql.dgs</groupId>
			<artifactId>graphql-dgs-spring-boot-starter</artifactId>
		</dependency>
		
		<dependency>
			<groupId>com.netflix.graphql.dgs</groupId>
			<artifactId>graphql-dgs-extended-scalars</artifactId>
		</dependency>

	</dependencies>Code language: Java (java)
https://gist.github.com/sureshgadupu/98ecb826e6a14c53c14c4d3cde0f33f9

Creating a Schema

Before developing API first we need to write the GraphQL schema file as DGS framework is designed for schema first development. The framework picks up any schema files in the src/main/resources/schema folder. Create a schema file in: src/main/resources/schema/schema.graphqls

For our example, I am going to use Employee database with 2 tables as shown below.I am using static data for this example.

In GraphQL schema language , entities are called Object Types. Each GraphQL service defines a set of Object Types. Object Types defines entities .Object Types also contains relationships between other types defined in schema.

Since each employee belongs to one department, we have formed relationships between employee nad Department

department: Department!

! – represents not null field

Inversly each Departmnet contains multiple Employees , it is represented by

employees:[Employee!]

[] – represents List of objects

type Employee {
  id:Int!
  first_name: String!
  last_name: String!
  gender: Gender
  birth_date: Date
  hire_date: Date	
  department: Department!
}

type Department {
  id : Int!
  dept_name : String!
  employees:[Employee!]
}

enum Gender {
 M
 F 
}
scalar DateCode language: Java (java)
https://gist.github.com/sureshgadupu/e7271bb992b44ec1ad951e8b4a543cb7

A GraphQL object type has a name and fields. Each field has dataype. In above schema For Employee object type id and first_name will resolve to integer and String. In GraphQL schema language they are called scalar types.

GraphQL comes with a set of default scalar types out of the box:

  • Int: A signed 32‐bit integer.
  • Float: A signed double-precision floating-point value.
  • String: A UTF‐8 character sequence.
  • Booleantrue or false.
  • ID: The ID scalar type represents a unique identifier, often used to refetch an object or as the key for a cache. The ID type is serialized in the same way as a String; however, defining it as an ID signifies that it is not intended to be human‐readable

In most GraphQL service implementations, there is also a way to specify custom scalar types. For example, we could define a Date type. Its upto us to provide implementation to define how that type should be serialized, deserialized, and validated.

DGS framework provides implementations for DateTime, Time, Json, Url scalar datatypes

Once you define object types , We need to have option to perform the CRUD operation on those Object Types. For this we need to declare Query, Mutation and Subscription types

Query type contains the all the retrieve operations we can perform on Object types. They are like ‘GET` methods in the REST API

Mutation type contains the all the Create/Update operations we can perform on Object types. They are like ‘POST and PUT` methods in the REST API

Subscription type defines subscription operations .subscriptions are long-lasting GraphQL read operations that can update their result whenever a particular server-side event occurs.Typically clients subscribes to changes in server. When ever a change occurs in the server, it notifies the subscribing clients of that change. For example, finance system can subscribe to createEmployee method so that it can set up salary for a newly joined employee.

type Query {   
   employee(id : Int) : Employee
   employees :[Employee!]
   departments :[Department!]
   getEmployeesByDeptId(deptId : Int) :[Employee!]
}Code language: Java (java)
https://gist.github.com/sureshgadupu/9b85a83cbb773dee61301238dcb1382f

Once we complete our schema, we need to write implementations for query methods. Classes which implement these methods are called DataFetchers or Resolvers.

package com.fullstackdev.graphql.eis.datafetcher;

import java.time.LocalDate;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collector;
import java.util.stream.Collectors;

import javax.annotation.PostConstruct;

import com.fullstackdev.graphql.eis.entity.Department;
import com.fullstackdev.graphql.eis.entity.Employee;
import com.fullstackdev.graphql.eis.entity.Gender;
import com.netflix.graphql.dgs.DgsComponent;
import com.netflix.graphql.dgs.DgsQuery;
import com.netflix.graphql.dgs.InputArgument;




@DgsComponent
public class EmployeeDataFetcher {
	
	Department hrDepartment =  new Department(1,"HR");
	Department finDepartment =   new Department(2,"Finance");
	Department mktDepartment =   new Department(3,"Marketing");
	Department engDepartment =   new Department(4,"Engineering");
	
	private final List<Employee> emps = List.of(
            new Employee(1,"Sally", "Bateman", Gender.M,LocalDate.of(1980, 12, 11), LocalDate.of(2014, 12, 2) ,hrDepartment ),
            new Employee(2,"Jessie", "Fraser", Gender.M,LocalDate.of(1981, 11, 2), LocalDate.of(2016, 8, 12) , finDepartment),
            new Employee(3,"Marlon", "Frost", Gender.F,LocalDate.of(1978, 12, 9), LocalDate.of(2001, 1, 20) , engDepartment),
            new Employee(4,"Shantelle", "Fitzpatrick", Gender.M,LocalDate.of(1975, 12, 12), LocalDate.of(2011, 8, 11) , mktDepartment),
            new Employee(5,"Hermione", "Zhang", Gender.F,LocalDate.of(1981, 12, 20), LocalDate.of(2011, 12, 20) ,hrDepartment ),
            new Employee(6,"Sana", "Sinclair", Gender.M,LocalDate.of(1982, 5, 16), LocalDate.of(2008, 12, 20) ,finDepartment),
            new Employee(7,"Charlie", "Morrow", Gender.F,LocalDate.of(1980,8, 15), LocalDate.of(2007, 12, 20) , engDepartment),
            new Employee(8,"Samina", "Donovan", Gender.M,LocalDate.of(1980, 12, 21), LocalDate.of(2005, 12, 20) , engDepartment),
            new Employee(9,"Hughie", "Huang", Gender.F,LocalDate.of(1980, 12, 12), LocalDate.of(2005, 12, 20) , engDepartment),
            new Employee(10,"Firat", "Hanson", Gender.M,LocalDate.of(1975, 2, 2), LocalDate.of(2004, 12, 20) , engDepartment)
           
    );
	
	@PostConstruct
	public void setEmpstoDept() {
		
		List.of(hrDepartment,finDepartment,mktDepartment,engDepartment).forEach(dept -> {			
			 dept.setEmployees(emps.stream().filter(emp -> emp.getDepartment().getId().equals(dept.getId())).collect(Collectors.toList()));
		});
	}

    
    @DgsQuery
    public Optional<Employee> employee(@InputArgument Integer id) {
       
        return emps.stream().filter(e -> e.getId().equals(id)).findFirst();

    }
    
    @DgsQuery
    public List<Employee> employees() {
       
        return  emps;

    }
    
    @DgsQuery
    public List<Department> departments() {
    	
        return   emps.stream().map(e -> e.getDepartment()).collect(Collectors.toList());

    }
    
    @DgsQuery
    public List<Employee> getEmployeesByDeptId(@InputArgument Integer deptId) {       
        return  emps.stream().filter(emp -> emp.getDepartment().getId().equals(deptId)).collect(Collectors.toList());

    }

}Code language: Java (java)
https://gist.github.com/sureshgadupu/f507c84a17a1dcf494d0c03e53c10310

We also need to register the scalar type Date. graphql-java provides optional scalars in the graphql-java-extended-scalars library. We can wire a scalar from this library by adding the scalar to the RuntimeWiring.

https://gist.github.com/sureshgadupu/772c832c20505314c2a460a039255723
@DgsComponent
public class DateScalar {
	
	@DgsRuntimeWiring
    public RuntimeWiring.Builder addScalar(RuntimeWiring.Builder builder) {
        return builder.scalar(ExtendedScalars.Date);
    }

}Code language: Java (java)

Testing GraphQL API

Now start the application and point your browser to http://localhost:8080/graphiql .

Now you can invoke queries defined in your schema on the lfet side panel and you will see the respose in right side panel.

You can see all the available queries , when you write query type and press the space.

GraphQL web client

Now let’s try the employee(id : Int) query.

In above image, you can observe that , since I am requesting only 2 fields in the request the response only contains only 2 fields.

Now let’s see another query in this query lets try to access Employee department and some other fields.

Now let’s try other queries

You can download source code for this blog post from GitHub.

Other posts in this series

Similar Posts