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.
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.
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.
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.
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.
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.
Create a project with Spring Initializer as a template. We will add only the Lombok here, as we will add the necessary dependencies later.
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:
@DgsComponent
.@ DgsData
, set parentType
to the schema type name, and field
to the field name.@InputArgument
or DataFetchingEnvironment
.Publisher
of reactive-streams
.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 you created. Since GraphiQL is included, start the Spring Boot application and access the endpoint (http: // localhost: 8080/graphiql
) with a browser.
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.
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>");
}
});
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