在Netflix DGS上使用Java实现大规模GraphQL服务器
由于Netflix的Tech博客上发布了GraphQL框架的OSS版本,所以我立刻尝试使用了它。
-
- How Netflix Scales its API with GraphQL Federation (Part 1)
- How Netflix Scales its API with GraphQL Federation (Part 2)
网飞 DGS
DGS(Domain Graph Service)是Netflix开发的用于构建Java/Kotlin的GraphQL服务器的框架。它旨在构建大规模的GraphQL服务,并采用了Apollo Federation。由于在Spring Boot上运行,因此成为非常易于操作的框架。它还支持Spring Security和从模式生成代码。请参阅官方文档以获取更详细的信息。
Netflix开发GraphQL框架的背景是什么?
真的很複雜呢。從前端的角度來看,由於有很多查詢服務端點,因此管理起來會很麻煩。
從服務端的角度來看,如果需要身份驗證等前置處理,每個微服務都需要實現相同的處理,可能會產生問題。
在Netflix中,我们决定采用GraphQL。采用GraphQL之后的架构如下所示。
前端的查询操作现在被收集到紫色部分,以GraphQL作为统一的结构,使得整体更加清晰简洁。
前端发出的请求通过GraphQL转发到各个微服务进行处理。
如果将前置处理(如认证)也实现在GraphQL部分,看起来就不需要在每个微服务中重复实现相同的处理逻辑了。
然而,这里存在一个问题。负责运营GraphQL的团队承担着巨大的工作负荷。
因此,我们采用了Apollo Federation并将负载分散到了GraphQL开发中。Apollo Federation采用后的情况如下图所示。
通过采用Apollo Federation,可以将GraphQL Gateway放置在前端,并将各个GraphQL服务部署在其后端,实现这样的架构是可能的。在上图中,紫色部分代表GraphQL Gateway,右侧的蓝色部分代表各个微服务的GraphQL。
各个微服务的GraphQL由各个服务团队运营管理,GraphQL Gateway则通过仅实现必要的最小限度功能,实现了GraphQL负载的分散。
确定要实施的功能
好的,现在我们将使用框架来进行实际实现。
我决定将之前写过的《使用Java实现GraphQL服务器》这篇文章中的同一功能(查询、变更、订阅)进行实现。以下是这些功能的使用案例。
-
- Query: 本の一覧を取得する。
-
- Query: 本のIDを指定して、特定の本を取得する。
-
- Mutation: 新しい本を登録する。
- Subscription: 新たに登録された本を通知する。
我本来想实现Federation,但目前还没找到有关Federation的文件,尤其是Gateway部分的实现我不太清楚。一旦有了文档,我会尝试着去实现。
构成项目。
该项目将使用Spring Initializer来创建一个适当的模板。由于需要的依赖项稍后添加,因此在这里只添加Lombok。
创造一个应用程序
构建.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 的中文表述
架构.graphqls
我們要定義GraphQL的模式。
將schema.graphqls檔案註冊在src/main/resources/schema資料夾中。
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模型
使用代码生成插件,从模式中创建Java类。
$ ./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
我成功创建了一个Book类。
DGS组件
然后,我们创建了一个实现了Query、Mutation和Subscription的Service类(DGS组件)。我们做了以下事情:
-
- クラスに@DgsComponentを付与します。
-
- メソッドに@DgsDataを付与し、parentTypeにはスキーマのtype name、fieldにはfield nameを設定します。
-
- fieldの引数は、@InputArgumentもしくはDataFetchingEnvironmentを使用して取得します。
- subscriptionのreturn値はreactive-streamsのPublisherを返します。
DataProvider 和 IBookProcessor 是我們自己建立的類別。執行以下的處理步驟:
-
- DataProvider : データ提供クラス。すべてのBookのList、または指定されたbookIdのBookを返す。
- IBookProcessor : 本の登録をイベントとして登録(emit)し、イベントのPublisherを発行(publish)する。
@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();
}
}
执行应用程序
执行创建的应用程序。由于附带了GraphiQL,因此启动SpringBoot应用程序,并在浏览器中访问端点(http://localhost:8080/graphiql)。
只有订阅出现错误。检查了调试日志后,发现以下错误已被输出。
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
对于Subscription,请发送请求到/subscriptions。
由于不知道如何更改GraphiQL的请求目标,所以我尝试实施如下客户端代码,并成功运行。
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>");
}
});
摘要
(mǎ
我已经描述了使用Netflix DGS实现GraphQL服务器的方法。
尽管我只是尝试了GraphQL的基本功能,但使用起来感觉还不错。
我很想尽快尝试Federation的部分。
我已经把编写好的源代码注册到了GitHub上。希望您可以参考。
- Simple application of GraphQL server with DGS