Implementing a large-scale GraphQL server in Java with Netflix DGS

The GraphQL Framework written in Netflix's Tech blog was released as OSS, so I immediately tried using it.

Netflix DGS

DGS (Domain Graph Service) is a framework for Java/Kotlin for building GraphQL servers developed by Netflix. We are assuming the construction of a large-scale GraphQL service and have adopted Apollo Federation. Since it runs on Spring Boot, it is a very easy-to-use framework. It also supports Spring Security and Code generation from schemas. For more information, see Official Documents.

Background to Netflix's development of the GraphQL framework

Netflix chose GraphQL to reduce the complexity of the API it exposes to the front end. Initially, each microservice had a public API, so it was in the following state. スクリーンショット 2021-01-17 20.59.42.png

It's very complicated. From the front end point of view, there are many server endpoints to contact, so it seems difficult to manage them. From a server-side perspective, if some pre-processing such as authentication is required, problems such as having to implement similar processing in each microservice are likely to occur.

So Netflix decided to use GraphQL. The figure after adopting GraphQL is as follows.

スクリーンショット 2021-01-17 21.06.33.png

The contact information for the front end is summarized in the purple part, GraphQL, and the structure is very neat. Requests from the front end are requested to each microservice via GraphQL. If preprocessing such as authentication is also implemented in the GraphQL part, it seems that it is not necessary to implement the same processing in each microservice.

However, there was one problem here. ** The load on the team that runs GraphQL is heavy. ** **

Therefore, we adopted Apollo Federation to distribute the load of developing GraphQL. The figure after adopting Apollo Federation is as follows.

スクリーンショット 2021-01-17 21.18.17.png

By adopting Apollo Federation, it is possible to configure GraphQL Gateway to be placed in the front stage and each GraphQL service to be placed in its back end. In the figure above, the purple part is GraphQL Gateway, and the blue part to the right is GraphQL for each microservice. GraphQL for each microservice is operated by each service team, and GraphQL Gateway implements only the minimum necessary to distribute the operational load of GraphQL.

Decide which function to implement

Now, let's actually implement it using the framework. I decided to implement the same functionality (Query, Mutation, Subscription) as the article I wrote earlier Implementing GraphQL Server in Java. The use cases of the function are as follows.

I wanted to implement Federation, but at the moment I can't find any documentation about Federation, especially the Gateway part implementation. I will implement it when the document is published.

Configure the project

Create a project with Spring Initializer as a template. We will add only the Lombok here, as we will add the necessary dependencies later.

Create an application

build.gradle

Make the following changes.

build.gradle


plugins {
	id 'org.springframework.boot' version '2.4.1'
	id 'io.spring.dependency-management' version '1.0.10.RELEASE'
	id 'java'
	// Added for "api".
	id 'java-library'
	// Added for code generation from scheme.
	id 'com.netflix.dgs.codegen' version '4.0.10'
}

sourceCompatibility = '11'

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
	// Added to fix error "Could not find com.apollographql.federation:federation-graphql-java-support".
	jcenter()
}

dependencies {
	api 'com.netflix.graphql.dgs:graphql-dgs-spring-boot-starter:latest.release'
	// Added for subscription.
	implementation 'com.netflix.graphql.dgs:graphql-dgs-subscriptions-websockets-autoconfigure:latest.release'

	implementation 'org.springframework.boot:spring-boot-starter-web'

	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'
}

// Added for code generation from scheme.
generateJava {
	schemaPaths = ["${projectDir}/src/main/resources/schema"]
	// Set package name of generated code.
	packageName = 'sandbox.dgs'
	// Set "false" not to generate client code.
	generateClient = false
}

schema.graphqls Defines the GraphQL schema. Register schema.graphqls under src/main/resources/schema.

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

Create a Java class from the schema using the Code generation plugin.

$ ./gradlew generateJava 
$ ls -l build/generated/sandbox/dgs
total 8
-rw-r--r--  1 xxxxxxx  yyyyy  942 Jan 11 19:48 DgsConstants.java
drwxr-xr-x  4 xxxxxxx  yyyyy  128 Jan 11 19:48 types

$ ls -l build/generated/sandbox/dgs/types
total 16
-rw-r--r--  1 xxxxxxx  yyyyy  2223 Jan 11 19:48 Book.java
-rw-r--r--  1 xxxxxxx  yyyyy  1487 Jan 11 19:48 Subscription.java

The Book class has been created.

DGS Component

Next, create a Service class (DGS component) that implements Query, Mutation, and Subscription. I did the following:

DataProvider and IBookProcessor are your own classes. Perform the following processing.

BookService.java


@AllArgsConstructor
@DgsComponent
public class BookService {
    private final DataProvider dataProvider;
    private final IBookProcessor bookProcessor;

    @DgsData(parentType = "Query", field = "books")
    public List<Book> books() {
        return dataProvider.books();
    }

    @DgsData(parentType = "Query", field = "bookById")
    public List<Book> books(@InputArgument("id") String id) {
        if (id == null || id.isEmpty()) {
            return dataProvider.books();
        }
        return List.of(dataProvider.bookById(id));
    }

    @DgsData(parentType = "Mutation", field = "registerBook")
    public Book registerBook(DataFetchingEnvironment dataFetchingEnvironment) {
        final String id = dataFetchingEnvironment.getArgument("id");
        final String name = dataFetchingEnvironment.getArgument("name");
        final int pageCount = dataFetchingEnvironment.getArgument("pageCount");

        final Book book = new Book(id, name, pageCount);
        dataProvider.books().add(book);
        // Emit an event for subscription.
        bookProcessor.emit(book);
        return book;
    }

    @DgsData(parentType = "Subscription", field = "subscribeBooks")
    public Publisher<Book> subscribeBooks() {
        return bookProcessor.publish();
    }
}

Run the application

Run the application you created. Since GraphiQL is included, start the Spring Boot application and access the endpoint (http: // localhost: 8080/graphiql) with a browser.

Only Subscription got an error. Looking at the debug log, the following error was output.

2021-01-11 21:05:52.305 DEBUG 70696 --- [nio-8080-exec-2] o.s.w.s.m.m.a.HttpEntityMethodProcessor  : Writing ["Trying to execute subscription on /graphql. Use /subscriptions instead!"]
2021-01-11 21:05:52.305 DEBUG 70696 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet        : Completed 400 BAD_REQUEST

For Subscription, it seems that you need to request / subscriptions. I didn't know how to change the GraphiQL request destination, so I implemented the following client code and tried it, and it worked fine.

main.ts


import { WebSocketLink } from "@apollo/client/link/ws";
import { SubscriptionClient } from 'subscriptions-transport-ws';
import { ApolloClient, InMemoryCache } from "@apollo/client";
import gql from 'graphql-tag';
import * as $ from 'jquery';

const GRAPHQL_ENDPOINT = 'ws://localhost:8080/subscriptions';

const client = new SubscriptionClient(GRAPHQL_ENDPOINT, {
  reconnect: true,
});

const link = new WebSocketLink(client);

const apolloClient = new ApolloClient({
  link: link,
  cache: new InMemoryCache()
});

const asGql = gql`
subscription BookSubscription {
  subscribeBooks {
    id,
    name
  }
}
`

const s = apolloClient.subscribe({
  query: asGql
})
s.subscribe({
  next: ({ data }) => {
    const result = document.getElementById("result");
    $("#result").append(JSON.stringify(data));
    $("#result").append("<br>");
  }
});

Summary

So far, we have described how to implement a GraphQL server using Netflix DGS. This time, I tried only the basic functions of GraphQL, but I had the impression that it was not bad for usability. I would like to try the Federation part as soon as possible.

The created source code is registered on the following GitHub. I would appreciate it if you could refer to it.

Recommended Posts

Implementing a large-scale GraphQL server in Java with Netflix DGS
Try implementing GraphQL server in Java
Split a string with ". (Dot)" in Java
Read a string in a PDF file with Java
Create a CSR with extended information in Java
Quickly implement a singleton with an enum in Java
What I learned when building a server in Java
Output true with if (a == 1 && a == 2 && a == 3) in Java (Invisible Identifier)
Update your Java knowledge by writing a gRPC server in Java (2)
Update your Java knowledge by writing a gRPC server in Java (1)
Find a subset in Java
Create a SlackBot with AWS lambda & API Gateway in Java
Even in Java, I want to output true with a == 1 && a == 2 && a == 3
Create a simple web server with the Java standard library com.sun.net.httpserver
I can't create a Java class with a specific name in IntelliJ
Try implementing Android Hilt in Java
Build a Java project with Gradle
Write a Reactive server with Micronaut
Morphological analysis in Java with Kuromoji
3 Implement a simple interpreter in Java
I created a PDF in Java.
Launch a stub server with WireMock
A simple sample callback in Java
Server processing with Java (Introduction part.1)
Get stuck in a Java primer
Play with Markdown in Java flexmark-java
[Personal memo] How to interact with a random number generator in Java
Even in Java, I want to output true with a == 1 && a == 2 && a == 3 (PowerMockito edition)
I wrote a Lambda function in Java and deployed it with SAM
I want to ForEach an array with a Lambda expression in Java
About returning a reference in a Java Getter
What is a class in Java language (3 /?)
When seeking multiple in a Java array
Concurrency Method in Java with basic example
[Creating] A memorandum about coding in Java
Java creates a table in a Word document
Java creates a pie chart in Excel
What is a class in Java language (1 /?)
What is a class in Java language (2 /?)
Create a TODO app in Java 7 Create Header
Try making a calculator app in Java
Read xlsx file in Java with Selenium
Get history from Zabbix server in Java
Implement something like a stack in Java
Creating a matrix class in Java Part 1
Working with huge JSON in Java Lambda
Configure a multi-project with subdirectories in Gradle
Try implementing GraphQL server using grahpql-java-tools (+ kotlin)
Let's make a calculator application with Java ~ Create a display area in the window
Even in Java, I want to output true with a == 1 && a == 2 && a == 3 (Javassist second decoction)
How to deploy a system created with Java (Wicket-Spring boot) to an on-campus server
Validate the identity token of a user authenticated with AWS Cognito in Java
Even in Java, I want to output true with a == 1 && a == 2 && a == 3 (black magic edition)
Let's create a TODO application in Java 12 Processing when a request comes in with an unused HttpMethod ・ Processing when an error occurs in the server