Microservices With GraphQL
A developer goes through some Java and XML code used to create a basic microservice application that uses GraphQL to fetch data.
Join the DZone community and get the full member experience.
Join For FreeGraphQL is an API that was invented and open sourced by Facebook as a better replacement for REST. It can be understood as Querby language for APIs, which enables declarative data fetching by exposing a single endpoint and responds to queries. In REST, there is always a dedicated endpoint for each type of request and can't be customized.
In GraphQL, the client decides what data they need and that's the reason the client sends a query (payload) to the server and the server sends the data back as per the query request. There is where they get the name GraphQL
Let's look at an example to understand the technical details. In this example, we will build a simple book store application using graphQL.
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.dugu.acc.dev</groupId>
<artifactId>spring-graphql</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>spring-graphql</name>
<description>GraphQL is invented by Facebook as a better replacement of REST for Web APIs</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version>
<relativePath />
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-spring-boot-starter</artifactId>
<version>3.6.0</version>
</dependency>
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java-tools</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
The GraphQL schema is the main concept of GraphQL, which is based on SDL (Schema Definition language). We can define the simple types as we can see 'type Book' in below example, and relations as well (for example, 'type Query' has a relation with Book). The relationship could be one to one, one to many, many to one, and many to many. In the below example, 'type Query' has a one to many (allBooks) and a one to one (Book) relationship. The schema is playing the major role to fetch the data.
The below file is under the src/main/resource folder of my GitHub repo (liked to at the end of the article).
book.schema
schema{
query: Query
}
type Query{
allBooks: [Book]
Book(id: String): Book
}
type Book{
bookId: String
bookName: String
publishedDate: String
writer: [String]
publisher: String
}
BookSearchController.java
package com.arun.spring.graphql.api.controller;
import java.io.File;
import java.io.IOException;
import java.util.List;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.arun.spring.graphql.api.datafetcher.BookDataFetcher;
import com.arun.spring.graphql.api.datafetcher.AllBookDataFetcher;
import com.arun.spring.graphql.api.entity.Book;
import com.arun.spring.graphql.api.service.BookService;
import graphql.ExecutionResult;
import graphql.GraphQL;
import graphql.schema.GraphQLSchema;
import graphql.schema.idl.RuntimeWiring;
import graphql.schema.idl.SchemaGenerator;
import graphql.schema.idl.SchemaParser;
import graphql.schema.idl.TypeDefinitionRegistry;
@RestController
@RequestMapping("/bookstore")
public class BookSearchController {
@Autowired
private BookService service;
// load graphqls file
@Value("classpath:book.schema")
private Resource schemaResource;
@Autowired
private AllBookDataFetcher allBookDataFetcher;
@Autowired
private BookDataFetcher bookDataFetcher;
private GraphQL graphQL;
// load schema at application start up
@PostConstruct
public void loadSchema() throws IOException {
// get the schema
File schemaFile = schemaResource.getFile();
// parse schema
TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(schemaFile);
RuntimeWiring wiring = buildRuntimeWiring();
GraphQLSchema schema = new SchemaGenerator().makeExecutableSchema(typeRegistry, wiring);
graphQL = GraphQL.newGraphQL(schema).build();
}
private RuntimeWiring buildRuntimeWiring() {
return RuntimeWiring.newRuntimeWiring().type("Query", typeWiring -> typeWiring
.dataFetcher("allBooks", allBookDataFetcher).dataFetcher("book", bookDataFetcher)).build();
}
@GetMapping("/booksList")
public List < Book > getBooksList() {
return service.findAllBooks();
}
/*
* In PostMan use Post URL: localhost:8080/bookstore/getAllBooks
* and Body: query{
allBooks{
bookId,
bookName
}
}
*/
@PostMapping("/getAllBooks")
public ResponseEntity < Object > getAllBooks(@RequestBody String query) {
ExecutionResult result = graphQL.execute(query);
return new ResponseEntity < Object > (result, HttpStatus.OK);
}
@GetMapping("/search/{bookId}")
public Book getBookInfo(@PathVariable String movieId) {
return service.findBookById(movieId);
}
@PostMapping("/getBookById")
public ResponseEntity < Object > getBookById(@RequestBody String query) {
ExecutionResult result = graphQL.execute(query);
return new ResponseEntity < Object > (result, HttpStatus.OK);
}
}
AllBookDataFetcher.java
package com.arun.spring.graphql.api.datafetcher;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.arun.spring.graphql.api.entity.Book;
import com.arun.spring.graphql.api.repository.BookRepository;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
@Component
public class AllBookDataFetcher implements DataFetcher < List < Book >> {
@Autowired
private BookRepository repository;
@Override
public List < Book > get(DataFetchingEnvironment environment) {
return repository.findAll();
}
}
BookDataFetcher.java
package com.arun.spring.graphql.api.datafetcher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.arun.spring.graphql.api.entity.Book;
import com.arun.spring.graphql.api.repository.BookRepository;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
@Component
public class BookDataFetcher implements DataFetcher < Book > {
@Autowired
private BookRepository repository;
@Override
public Book get(DataFetchingEnvironment environment) {
String movieId = environment.getArgument("id");
return repository.findOne(movieId);
}
}
Book.java
package com.arun.spring.graphql.api.entity;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.ToString;
@ToString
@AllArgsConstructor
@NoArgsConstructor
@Table
@Entity
public class Book {
@Id
private String bookId;
private String bookName;
private String publishedDate;
private String[] writer;
private String publisher;
}
BookRepository.java
package com.arun.spring.graphql.api.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import com.arun.spring.graphql.api.entity.Book;
public interface BookRepository extends JpaRepository<Book, String> {
}
BookService.java
package com.arun.spring.graphql.api.service;
import java.util.*;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.arun.spring.graphql.api.entity.Book;
import com.arun.spring.graphql.api.repository.BookRepository;
@Service
public class BookService {
@Autowired
private BookRepository repository;
@PostConstruct
public void initBooks() {
List < Book > books = new ArrayList < > ();
books.add(new Book("101", "The Science of Marvel",
"22-12-2017", new String[] {
"Sebastian"
},
"Infinity Stones"));
books.add(new Book("102", "The Sixth Extinction",
"22-12-2017", new String[] {
"Sebastian",
"Elizabeth"
},
"Infinity Stones"));
books.add(new Book("103", "The Science of Marvel -2",
"22-12-2019", new String[] {
"Sebastian"
},
"Infinity Stones"));
repository.save(books);
}
public List < Book > findAllBooks() {
return repository.findAll();
}
public Book findBookById(String movieId) {
return repository.findOne(movieId);
}
}
SpringGraphqlApplication.java
package com.arun.spring.graphql.api;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringGraphqlApplication {
public static void main(String[] args) {
SpringApplication.run(SpringGraphqlApplication.class, args);
}
}
Start the SpringGraphqlApplication and then call the GraphQL endpoint as shown below using Postman:
In Postman, use the Post URL: localhost:8080/bookstore/getAllBooks
and Body: query{ allBooks{bookId,bookName }}
You can add more fields in the body part and, accordingly, it will retrieve the data from server.
That's all for this talk. Enjoy the power of GraphQL.
GitHub URL: https://github.com/arunpandeycdac/MicroserviceWithSpringBootAndGraphQL
Opinions expressed by DZone contributors are their own.
Comments