Hello. Personally, I had the opportunity to implement a GraphQL server in Java, so this time I would like to write about that finding. This article is the 18th day article of GraphQL Advent Calendar 2020.
This article does not provide an overview of GraphQL or a description of the Spring Boot and its peripheral libraries used in its implementation. I think that those who know them to some extent will be the target, but since I have not written anything so difficult, I think that you can probably understand it in the atmosphere.
First, find the libraries you need to implement your GraphQL server.
Two were found. Both seem to be usable in combination with Spring Boot. Looking at the implementation example of GraphQL Resolver, GraphQL Java Kickstart seemed to be simpler to write, so I will use this one this time.
Looking at the GraphQL Java Kickstart README,
This project wraps the Java implementation of GraphQL provided by GraphQL Java.
It wraps GraphQL Java. Is written. So that's it.
Next, decide which features to implement using GraphQL. This time, I decided to implement the basic functions using Query, Mutation, and Subscription. The use cases of the function are roughly as follows.
Now let's create an application. Create a project with Spring Initializer as a template. Only Lombok is added here because we will add the necessary dependencies later.
build.gradle First, modify build.gradle.
Add com.graphql-java-kickstart: graphql-spring-boot-starter
to dependency.
Also add com.graphql-java-kickstart: graphiql-spring-boot-starter
to test the API on GraphQL.
In addition, the dependencies of io.projectreactor: reactor-core
, spring-actuator
, and micrometer-registry-prometheus
have been added. The former is for Subscription implementation and the latter is for Metrics acquisition.
build.gradle
plugins {
id 'org.springframework.boot' version '2.4.1'
id 'io.spring.dependency-management' version '1.0.10.RELEASE'
id 'java'
}
sourceCompatibility = '11'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'com.graphql-java-kickstart:graphql-spring-boot-starter:8.0.0'
runtimeOnly 'com.graphql-java-kickstart:graphiql-spring-boot-starter:8.0.0'
// To embed GraphiQL tool
implementation 'com.graphql-java-kickstart:graphiql-spring-boot-starter:8.0.0'
// For subscripion
implementation 'io.projectreactor:reactor-core:3.4.1'
// For metrics
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'io.micrometer:micrometer-registry-prometheus:1.6.0'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
}
application.yml
Next, create application.yml
.
1. To enable graphiql
Specify the GraphQL URL alias and GraphQL Aquespoint URL with the following settings.2. To enable graphql metrics
is a setting to get Metrics related to GraphQL.3. To enable to get metrics ..
is the spring-actuator setting. You can get each Metrics in Prometheus format by visiting / actuator/prometheus
.application.yml
# 1. To enable graphiql
graphiql:
mapping: /graphiql
endpoint:
graphql: /graphql
# 2. To enable graphql metrics
graphql:
servlet:
actuator-metrics: true
# 3. To enable to get metrics of spring-actuator from /actuator/prometheus
management:
endpoints:
web:
exposure:
include: health,metrics,prometheus
schema.graphqls
Create schema.graphqls
to define the GraphQL schema.
Register schema.graphqls
under src/main/resources/graphsql
.
schema.graphqls
type Query {
bookById(id: ID): Book
books: [Book]!
}
type Book {
id: ID
name: String
pageCount: Int
}
type Mutation {
registerBook (
id: ID
name: String
pageCount: Int
): Book
}
type Subscription {
subscribeBooks: Book!
}
Java Model GraphQL Java requires Java classes defined in the schema. First, create a Java Model for Book Type.
Book.java
@AllArgsConstructor
@Data
public class Book {
private String id;
private String name;
private int pageCount;
}
Resolver Next, create a Resolver that implements Query, Mutation, and Subscription. It is necessary to create the following implementation classes for each, and make the query name defined in the schema and the implemented method name the same. Also, the implementation class is registered as a Spring Bean.
GraphQLQueryResolver
and add a method with the query name and arguments defined in the schema.GraphQLMutationResolver
and add a method with the Mutation name and arguments defined in the schema.GraphQLSubscriptionResolver
and implement a method with the Subscription name and arguments defined in the schema. The Return value must be Publisher
of reactive-streams
.DataProvider
and IBookProcessor
are your own classes. Perform the following processing.
Book
s, or the Book
of the specified bookId
.emit
) and publishes a publisher of the event (publish
).BookResolver.java
@Slf4j
@AllArgsConstructor
@Component
public class BookResolver implements GraphQLQueryResolver,
GraphQLMutationResolver,
GraphQLSubscriptionResolver {
private final DataProvider dataProvider;
private final IBookProcessor bookProcessor;
/**
* Query: Get all books.
*/
public List<Book> books() {
return dataProvider.books();
}
/**
* Query: Retrieve a book by id.
*/
public Book bookById(String bookId) {
return dataProvider.bookById(bookId);
}
/**
* Mutation: Register a book.
*/
public Book registerBook(String id, String name, int pageCount) {
final Book book = new Book(id, name, pageCount);
dataProvider.books().add(book);
// Emit an event for subscription.
bookProcessor.emit(book);
return book;
}
/**
* Subscription: Publish an event that a book is registered.
* Need to return Publisher on reactive-streams.
*/
public Publisher<Book> subscribeBooks() {
return bookProcessor.publish();
}
/**
* Error handler. can handle an throwable that occurs in resolver execution.
*/
@ExceptionHandler(Throwable.class)
GraphQLError handle(Throwable e) {
log.error("Failed to execute resolver.", e);
return new ThrowableGraphQLError(e, "Failed to execute resolver.");
}
}
I tried to make it easy to understand.
Run the application you created. Start the SpringBoot application, access the GraphQL endpoint (http: // localhost: 8080/graphiql
) with a browser, and execute Query.
Query: Get a list of books.
Query: Get a specific book by specifying the book ID.
Mutation: Register a new book.
Subscription: Notify you of newly registered books. It's a little difficult to understand, but the image above is the image after performing the following operations.
Metrics were also collected correctly. I use a tool called Metricat on the following site for graphing.
If you set the prometheus endpoint of spring-actuator (http: // localhost: 8080/actuator/prometheus
) to the Prometheus exporter URL
of Metricat and then execute the query with GraphiQL, the following graph will be displayed. I will.
So far, we have described how to implement GraphQL server in Java. The implemented code is just a sample code level, but it was easy to develop such as Resolver, and it was not bad for usability.
I have no plans to use it in practice yet, but I've listed some points that I'm curious about when I imagine using it in practice.
CompletionStage
(implementation class: CompletableFuture
) can be returned. Async Resolvers # 1The source code used is registered on the following GitHub. I would appreciate it if you could refer to it.
Recommended Posts