DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workkloads.

Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Distributed Tracing System (Spring Cloud Sleuth + OpenZipkin)
  • Full-Duplex Scalable Client-Server Communication with WebSockets and Spring Boot (Part I)
  • How To Build Web Service Using Spring Boot 2.x
  • Google Cloud Pub/Sub: Messaging With Spring Boot 2.5

Trending

  • The Role of Functional Programming in Modern Software Development
  • The Cypress Edge: Next-Level Testing Strategies for React Developers
  • Transforming AI-Driven Data Analytics with DeepSeek: A New Era of Intelligent Insights
  • Rust and WebAssembly: Unlocking High-Performance Web Apps
  1. DZone
  2. Data Engineering
  3. Databases
  4. GraphQL — The Future of Microservices?

GraphQL — The Future of Microservices?

Today, we'll talk about how to use GraphQL with Spring Boot microservices and the advantages over REST APIs.

By 
Piotr Mińkowski user avatar
Piotr Mińkowski
·
Aug. 22, 18 · Tutorial
Likes (13)
Comment
Save
Tweet
Share
25.2K Views

Join the DZone community and get the full member experience.

Join For Free

Often, GraphQL is presented as a revolutionary way of designing web APIs in comparison to REST. However, if you would take a closer look at that technology, you will see that there are so many differences between them. GraphQL is a relatively new solution that was open sourced by Facebook in 2015. Today, REST is still the most popular paradigm used for exposing APIs and inter-service communication between microservices. Is GraphQL going to overtake REST in the future? Let's take a look how to create microservices communicating through GraphQL API using Spring Boot and Apollo client.

Let's begin with an architecture of our sample system. We have three microservices that communicate with each other using URLs taken from Eureka service discovery.

1. Enabling Spring Boot Support for GraphQL

We can easily enable support for GraphQL in the server-side Spring Boot application by including some starters. After including graphql-spring-boot-starter, the GraphQL servlet will be automatically accessible under the path /graphql. We can override that default path with the settings property graphql.servlet.mapping in the application.yml file. We should also enable GraphiQL, an in-browser IDE for writing, validating, and testing GraphQL queries, and GraphQL Java Tools library, which contains useful components for creating queries and mutations. Thanks to that library, any files on the classpath with the .graphqls extension will be used to provide the schema definition.

<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-spring-boot-starter</artifactId>
<version>5.0.2</version>
</dependency>
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphiql-spring-boot-starter</artifactId>
<version>5.0.2</version>
</dependency>
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java-tools</artifactId>
<version>5.2.3</version>
</dependency>

2. Building a GraphQL Schema Definition

Every schema definition contains data type declarations, relationships between them, and a set of operations including queries for searching objects and mutations for creating, updating, or deleting data. Usually, we will start by creating a type declaration, which is responsible for domain object definition. You can specify if the field is required using ! or if it is an array using [...]. The definition has to contain a type declaration or reference to other types available in the specification.

type Employee {
  id: ID!
  organizationId: Int!
  departmentId: Int!
  name: String!
  age: Int!
  position: String!
  salary: Int!
}

Here's an equivalent Java class to the GraphQL definition visible above. The GraphQL type Int can be also mapped to Java Long. The ID scalar type represents a unique identifier; in that case, it also would be Java Long.

public class Employee {

private Long id;
private Long organizationId;
private Long departmentId;
private String name;
private int age;
private String position;
private int salary;

// constructor

// getters
// setters

}

The next part of the schema definition contains queries and mutations declarations. Most of the queries return a list of objects marked with [Employee]. Inside the EmployeeQueries type, we have declared all find methods, while inside the EmployeeMutations type, methods for adding, updating, and removing employees. If you pass the whole object to that method, you need to declare it as an input type.

schema {
  query: EmployeeQueries
  mutation: EmployeeMutations
}

type EmployeeQueries {
  employees: [Employee]
  employee(id: ID!): Employee!
  employeesByOrganization(organizationId: Int!): [Employee]
  employeesByDepartment(departmentId: Int!): [Employee]
}

type EmployeeMutations {
  newEmployee(employee: EmployeeInput!): Employee
  deleteEmployee(id: ID!) : Boolean
  updateEmployee(id: ID!, employee: EmployeeInput!): Employee
}

input EmployeeInput {
  organizationId: Int
  departmentId: Int
  name: String
  age: Int
  position: String
  salary: Int
}

3. Queries and Mutation Implementation

Thanks to GraphQL Java Tools and Spring Boot GraphQL auto-configuration, we don't need to do much to implement queries and mutations in our application. The EmployeesQuery bean has theGraphQLQueryResolver interface. Based on that, Spring will be able to automatically detect and call the right method as a response to one of the GraphQL queries declared inside the schema. Here's a class containing an implementation of queries.

@Component
public class EmployeeQueries implements GraphQLQueryResolver {

 private static final Logger LOGGER = LoggerFactory.getLogger(EmployeeQueries.class);

 @Autowired
 EmployeeRepository repository;

 public List employees() {
  LOGGER.info("Employees find");
  return repository.findAll();
 }

 public List employeesByOrganization(Long organizationId) {
  LOGGER.info("Employees find: organizationId={}", organizationId);
  return repository.findByOrganization(organizationId);
 }

 public List employeesByDepartment(Long departmentId) {
  LOGGER.info("Employees find: departmentId={}", departmentId);
  return repository.findByDepartment(departmentId);
 }

 public Employee employee(Long id) {
  LOGGER.info("Employee find: id={}", id);
  return repository.findById(id);
 }

}

If you would like to call, for example, the method employee(Long id), you should build the following query. You can easily test it in your application using the GraphiQL tool available under the path /graphiql.

The bean responsible for implementation of mutation methods needs to implement GraphQLMutationResolver. Despite the declaration of EmployeeInput, we still use the same domain object as returned by queries: Employee.

@Component
public class EmployeeMutations implements GraphQLMutationResolver {

 private static final Logger LOGGER = LoggerFactory.getLogger(EmployeeQueries.class);

 @Autowired
 EmployeeRepository repository;

 public Employee newEmployee(Employee employee) {
  LOGGER.info("Employee add: employee={}", employee);
  return repository.add(employee);
 }

 public boolean deleteEmployee(Long id) {
  LOGGER.info("Employee delete: id={}", id);
  return repository.delete(id);
 }

 public Employee updateEmployee(Long id, Employee employee) {
  LOGGER.info("Employee update: id={}, employee={}", id, employee);
  return repository.update(id, employee);
 }

}

We can also use GraphiQL to test mutations. Here's the command that adds a new employee and receives a response with the employee's id and name.

4. Generating Client-Side Classes

We have successfully created a server-side application and we have already tested some queries using GraphiQL, but our main goal is to create another microservices that communicates with the employee-service application through a GraphQL API. Here is where most tutorials about Spring Boot and GraphQL end.

To be able to communicate with our first application through a GraphQL API, we have two choices. We can get a standard REST client and implement the GraphQL API by ourselves with HTTP GET requests or using an existing Java client. Surprisingly, there are not many GraphQL Java client implementations available. The most serious choice is Apollo GraphQL Client for Android. Of course, it is not designed only for Android devices, and you can successfully use it in your Java microservice application.

Before using the client, we need to generate classes from the schema and.graphql files. The recommended way to do this is through the Apollo Gradle Plugin. There are also some Maven plugins, but none of them provide the same level of automation as Gradle plugin. For example, it automatically downloads the Node.js required for generating client-side classes. So, the first step is to add the Apollo plugin and runtime to the project dependencies.

buildscript {
  repositories {
    jcenter()
    maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
  }
  dependencies {
    classpath 'com.apollographql.apollo:apollo-gradle-plugin:1.0.1-SNAPSHOT'
  }
}

apply plugin: 'com.apollographql.android'

dependencies {
  compile 'com.apollographql.apollo:apollo-runtime:1.0.1-SNAPSHOT'
}

The GraphQL Gradle plugin tries to find files with the .graphql extension and schema.json inside the src/main/graphql directory. The GraphQL JSON schema can be obtained from your Spring Boot application by calling the resource /graphql/schema.json. The file .graphql contains queries definitions. The query employeesByOrganization will be called by organization-service, while employeesByDepartment by both department-service and organization-service. Those two applications need a different set of data in the response. The application department-service requires more detailed information about every employee than organization-service. GraphQL is an excellent solution in that case because we can define the required set of data in the response on the client side. Here's query definition of employeesByOrganization called by organization-service.

query EmployeesByOrganization($organizationId: Int!) {
  employeesByOrganization(organizationId: $organizationId) {
    id
    name
  }
}

The application organization-service would also call the employeesByDepartment query.

query EmployeesByDepartment($departmentId: Int!) {
  employeesByDepartment(departmentId: $departmentId) {
    id
    name
  }
}

The query employeesByDepartment is also called by department-service, which requires not only id and name fields, but also position and salary.

query EmployeesByDepartment($departmentId: Int!) {
  employeesByDepartment(departmentId: $departmentId) {
    id
    name
    position
    salary
  }
}

All the generated classes are available under the build/generated/source/apollo directory.

5. Building the Apollo Client With Discovery

After generating all required classes and including them in calling microservices, we may proceed to the client implementation. The Apollo client has two important features that will affect our development:

  • It provides only asynchronous methods based on callback.
  • It does not integrate with service discovery based on Spring Cloud Netflix Eureka.

Here's an implementation of the employee-service client inside department-service. I used EurekaClient directly (1). It gets all running instances registered as EMPLOYEE-SERVICE. Then it selects one instance from the list of available instances randomly (2). The port number of that instance is passed to ApolloClient(3). Before calling the asynchronous method enqueue provided by ApolloClient, we create a lock (4) which waits a maximum of 5 seconds before releasing (8). The method enqueue returns a response in the callback method onResponse(5). We map the response body from the GraphQL Employee object to the returned object (6) and then release the lock (7).

@Component
public class EmployeeClient {

 private static final Logger LOGGER = LoggerFactory.getLogger(EmployeeClient.class);
 private static final int TIMEOUT = 5000;
 private static final String SERVICE_NAME = "EMPLOYEE-SERVICE";
 private static final String SERVER_URL = "http://localhost:%d/graphql";

 Random r = new Random();

 @Autowired
 private EurekaClient discoveryClient; // (1)

 public List findByDepartment(Long departmentId) throws InterruptedException {
  List employees = new ArrayList();
  Application app = discoveryClient.getApplication(SERVICE_NAME); // (2)
  InstanceInfo ii = app.getInstances().get(r.nextInt(app.size()));
  ApolloClient client = ApolloClient.builder().serverUrl(String.format(SERVER_URL, ii.getPort())).build(); // (3)
  CountDownLatch lock = new CountDownLatch(1); // (4)
  client.query(EmployeesByDepartmentQuery.builder().build()).enqueue(new Callback() {

   @Override
   public void onFailure(ApolloException ex) {
    LOGGER.info("Err: {}", ex);
    lock.countDown();
   }

   @Override
   public void onResponse(Response res) { // (5)
    LOGGER.info("Res: {}", res);
    employees.addAll(res.data().employees().stream().map(emp -> new Employee(Long.valueOf(emp.id()), emp.name(), emp.position(), emp.salary())).collect(Collectors.toList())); // (6)
    lock.countDown(); // (7)
   }

  });
  lock.await(TIMEOUT, TimeUnit.MILLISECONDS); // (8)
  return employees;
 }

}

Finally, EmployeeClient is injected into the query resolver class DepartmentQueries, and used inside the query departmentsByOrganizationWithEmployees.

@Component
public class DepartmentQueries implements GraphQLQueryResolver {

 private static final Logger LOGGER = LoggerFactory.getLogger(DepartmentQueries.class);

 @Autowired
 EmployeeClient employeeClient;
 @Autowired
 DepartmentRepository repository;

 public List departmentsByOrganizationWithEmployees(Long organizationId) {
  LOGGER.info("Departments find: organizationId={}", organizationId);
  List departments = repository.findByOrganization(organizationId);
  departments.forEach(d -> {
   try {
    d.setEmployees(employeeClient.findByDepartment(d.getId()));
   } catch (InterruptedException e) {
    LOGGER.error("Error calling employee-service", e);
   }
  });
  return departments;
 }

 // other queries

}

Before calling the target query, we should take a look at the schema created for department-service. Every Department object can contain the list of assigned employees, so we also define the type Employee referenced by Department type.

schema {
  query: DepartmentQueries
  mutation: DepartmentMutations
}

type DepartmentQueries {
  departments: [Department]
  department(id: ID!): Department!
  departmentsByOrganization(organizationId: Int!): [Department]
  departmentsByOrganizationWithEmployees(organizationId: Int!): [Department]
}

type DepartmentMutations {
  newDepartment(department: DepartmentInput!): Department
  deleteDepartment(id: ID!) : Boolean
  updateDepartment(id: ID!, department: DepartmentInput!): Department
}

input DepartmentInput {
  organizationId: Int!
  name: String!
}

type Department {
  id: ID!
  organizationId: Int!
  name: String!
  employees: [Employee]
}

type Employee {
  id: ID!
  name: String!
  position: String!
  salary: Int!
}

Now we can call our test query with a list of required fields using GraphiQL. The application department-service is by default available under port 8091, so we may call it using the address http://localhost:8091/graphiql.

Conclusion

GraphQL seems to be an interesting alternative to standard REST APIs. However, we should not consider it a replacement for REST. There are some use cases where GraphQL may be the better choice, and some use cases where REST is the better choice. If your client does not need the full set of fields returned by the server side, and moreover, you have many clients with different requirements for a single endpoint, GraphQL is a good choice. When it comes to microservices, there are no solutions based on Java that allow you to use GraphQL together with service discovery, load balancing, or API gateway out-of-the-box. In this article, I have shown an example of using Apollo GraphQL clients together with Spring Cloud Eureka for inter-service communication. The sample application's source code is available on GitHub.


GraphQL Spring Framework microservice Spring Boot Database application Spring Cloud Java (programming language) Threading Schema

Published at DZone with permission of Piotr Mińkowski, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Distributed Tracing System (Spring Cloud Sleuth + OpenZipkin)
  • Full-Duplex Scalable Client-Server Communication with WebSockets and Spring Boot (Part I)
  • How To Build Web Service Using Spring Boot 2.x
  • Google Cloud Pub/Sub: Messaging With Spring Boot 2.5

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!