DGS框架基于Spring Boot,因此,如果您还没有一个新的Spring Boot应用程序,请先开始。Spring Initializr是一种简便的方法。您可以使用Gradle或Maven,Java 8或更高版本,也可以使用Kotlin。我们推荐Gradle,因为我们有一个非常酷的代码生成插件!
唯一需要的Spring依赖项是Spring Web。
在IDE中打开项目(建议使用Intellij)。
将com.netflix.graphql.dgs:graphql-dgs-spring-boot-starter依赖项添加到您的Gradle或Maven配置中。
xsi:schemaLocation='http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd'
xmlns='http://maven.apache.org/SETTINGS/1.0.0'>
请注意,只有JVM库上的Apollo Federation才需要jcenter配置,该库当前仅在JCenter上可用。他们正在向Maven Central发布出版物。
在样例程序中我使用的是Gradle ,通过生成的schema 文件生成 JAVA 代码。
在生成实体的过程中,有可能遇到自定义类型或是复杂类型,可以通过配置Gradle 文件中的映射描述,处理此类问题:
generateJava{
typeMapping = ["Date": "java.util.Date"]
schemaPaths = ["${projectDir}/src/main/resources/schema"] // List of directories containing schema files
packageName = 'com.chinasofti.broker.hw.demo' // The package name to use to generate sources
generateClient = true // Enable generating the type safe query API
}
例如,在创建实体是遇到了Date 类型,Date类型在GraphQL中不存在映射,需要配置。
typeMapping = ["Date": "java.util.Date"]
这样在生成JAVA 代码时会解析此类型,并生成正确的导入类。
package com.chinasofti.broker.hw.demo.types;
import java.lang.Object;
import java.lang.Override;
import java.lang.String;
import java.util.Date;
public class THwOrder {
private String id;
private Date toDbTime;
private Integer pushStatus;
DGS框架旨在用于架构优先开发。框架会检索src/main/resources/schema路径中的所有schema文件。在制定路径创建scheam文件src/main/resources/schema/schema.graphqls。
type Query { shows(titleFilter: String): [Show]}type Show { title: String releaseYear: Int}
该模式允许查询列表,可以选择按标题过滤。
数据提取程序负责为查询返回数据。创建两个新类example.ShowsDataFetcher,Show并添加以下代码。
JAVA
@DgsComponentpublic
class ShowsDatafetcher {
private final Listshows = List.of(new Show("Stranger Things", 2016), new Show("Ozark", 2017), new Show("The Crown", 2016), new Show("Dead to Me", 2019), new Show("Orange is the New Black", 2013));
@DgsData(parentType = "Query", field = "shows")
public Listshows(@InputArgument("titleFilter") String titleFilter) {
if (titleFilter == null) {
return shows;
}
return shows.stream().filter(s -> s.getTitle().contains(titleFilter)).collect(Collectors.toList());
}
}
public class Show {
private final String title;
private final Integer releaseYear;
public Show(String title, Integer releaseYear) {
this.title = title;
this.releaseYear = releaseYear;
}
public String getTitle() {
return title;
}
public Integer getReleaseYear() {
return releaseYear;
}
}
您可能已经注意到使用@InputArgument来从数据获取环境中提取输入参数。这应该适用于大多数的输入类型,比如String,Integer定制标量和输入对象。
java
@DgsData(parentType = DgsConstants.MUTATION.TYPE_NAME, field = DgsConstants.MUTATION.AddReview)
public List
reviewsService.saveReview(reviewInput);
List
return Objects.requireNonNullElseGet(reviews, List::of);
}
以上是适用于代表标量或定制的标量最列表类型,如List
转存失败重新上传取消java
@DgsData(parentType = DgsConstants.MUTATION.TYPE_NAME, field = DgsConstants.MUTATION.AddReviews)
public List
reviewsService.saveReviews(reviewsInput);
List
Map
return new ArrayList(reviews.values());
}
启动应用程序,然后打开浏览器到http:// localhost:8080 / graphiql。GraphiQL是随DGS框架一起提供的查询编辑器。编写以下查询并测试结果。
{ shows { title releaseYear }}
请注意,与REST不同,您必须明确列出要从查询中返回的字段。这是GraphQL强大功能的发源地,但令GraphQL新手感到惊讶。
GraphiQL编辑器实际上只是一个使用/graphql服务端点的UI 。您现在也可以将UI连接到您的后端,例如使用React和Apollo Client。
无参调用:
{
shows {
title
releaseYear
}
}
响应:
{
"data": {
"shows": [
{
"title": "Stranger Things",
"releaseYear": 2016
},
{
"title": "Ozark",
"releaseYear": 2017
},
{
"title": "The Crown",
"releaseYear": 2016
},
{
"title": "Dead to Me",
"releaseYear": 2019
},
{
"title": "Orange is the New Black",
"releaseYear": 2013
}
]
}
}
带参数调用:
{
shows(titleFilter:"Stranger Things") {
title
releaseYear
}
}
对应:
响应:
{
"data": {
"shows": [
{
"title": "Stranger Things",
"releaseYear": 2016
}
]
}
}
其它官方示例代码:
https://github.com/Netflix/dgs-examples-java
import com.chinasofti.broker.hw.dgs.ShowsDatafetcher;
import com.netflix.graphql.dgs.DgsQueryExecutor;
import com.netflix.graphql.dgs.autoconfig.DgsAutoConfiguration;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(classes = {DgsAutoConfiguration.class, ShowsDatafetcher.class})
class ShowsDatafetcherTest {
@Autowired
DgsQueryExecutor dgsQueryExecutor;
@Test
void shows() {
List
" { shows { title releaseYear }}",
"data.shows[*].title");
assertThat(titles).contains("Ozark");
}
}
该@SpringBootTest注解表明这是一个Spring测试。如果未classes明确指定,Spring将在类路径上启动所有组件。对于小型应用程序,这样编写没毛病,但是对于具有“复杂”启动组件的应用程序,我们可以通过仅添加测试所需的类来加快测试速度。在这种情况下,我们需要使用DgsAutoConfiguration类和来包含DGS框架本身ShowsDatafetcher。
要执行查询,请插入DgsQueryExecutor测试。该接口有几种执行查询并取回结果的方法。它执行与/graphql端点上查询完全相同的代码,这样就不必在测试中处理HTTP。这些DgsQueryExecutor方法接受JSON路径,因此这些方法可以轻松地从您感兴趣的响应中仅提取数据。DgsQueryExecutor还包括executeAndExtractJsonPathAsObject将结果反序列化为Java类的方法,该类在后台使用Jackson。开源JsonPath库支持JSON路径。
再编写一些测试,例如,使用titleFilterof验证行为ShowsDatafetcher。您可以从IDE或Gradle / Maven运行测试,就像任何JUnit测试一样。
在前面的示例中,我们手工制作了查询字符串。对于简单的查询来说,这就足够了。但是,构造更长的查询字符串可能很繁琐,尤其是在Java中,不支持多行字符串。为此,我们可以将GraphQLQueryRequest与代码生成插件结合使用来构建graphql请求,以生成使用请求构建器所需的类。这提供了一种方便的类型安全的方式来构建查询。
要设置代码生成以生成用于构建查询的必需类,请按照此处的说明进行操作。
现在,我们可以编写一个GraphQLQueryRequest用于构建查询并使用提取响应的测试GraphQLResponse。
自动生成插件:
将构建配置文件修改为如下内容:
buildscript {
dependencies{
classpath 'com.netflix.graphql.dgs.codegen:graphql-dgs-codegen-gradle:latest.release'
}
}
plugins {
id 'org.springframework.boot' version '2.3.10.BUILD-SNAPSHOT'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
// Using plugins DSL
id "com.netflix.dgs.codegen" version "4.0.10"
}
apply plugin: 'com.netflix.dgs.codegen'
generateJava{
typeMapping = ["MyGraphQLType": "com.chinasofti.broker.hw.demo"]
schemaPaths = ["${projectDir}/src/main/resources/schema"] // List of directories containing schema files
packageName = 'com.chinasofti.broker.hw.demo' // The package name to use to generate sources
generateClient = true // Enable generating the type safe query API
}
group = 'com.chinasofti.broker'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
jcenter()
maven { url 'https://repo.spring.io/milestone' }
maven { url 'https://repo.spring.io/snapshot' }
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation "com.netflix.graphql.dgs:graphql-dgs-spring-boot-starter:latest.release"
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
annotationProcessor 'org.projectlombok:lombok'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
}
test {
useJUnitPlatform()
}
代码生成后的目录结构: 生成的代码不需要复制到src目录。
异常使用:
GraphQL通常会遇到两种错误:
GraphQL规范对错误的结构提供了最小限度的指导。唯一需要的字段是message字符串,没有定义的格式。在Studio Edge中,我们希望有一个更强大,更具表现力的合同。这是我们正在使用的定义:
场地 |
类型 |
描述 |
message (不可为空) |
String! |
错误的字符串描述,供开发人员参考,以帮助您理解和更正错误 |
locations |
[Location] |
代码位置的数组,其中每个位置都是带有键line和的映射column,两个自然数均从1开始,描述相关语法元素的开头 |
path |
[String | Int] |
如果错误与响应中的一个或多个特定字段相关联,则错误的此字段详细说明经历错误的那些响应字段的路径(这使客户端可以识别null结果是故意的还是由运行时错误引起的) |
extensions |
[TypedError] |
请参阅下面的“ TypedError接口” |
有关更多信息,请参见GraphQL规范:错误。
Studio Edge定义TypedError如下:
场地 |
类型 |
描述 |
errorType (不可为空) |
ErrorType! |
枚举的错误代码,表示错误的简单的描述,足以满足客户端分支逻辑的需要 |
errorDetail |
ErrorDetail |
提供有关错误的详细信息的枚举,包括其具体原因(此枚举的元素可能会更改,在此不做记录) |
origin |
String |
发出错误的源的名称(例如,后端服务,[DGS],网关,客户端库或客户端应用的名称) |
debugInfo |
DebugInfo |
如果请求中包含指示其需要调试信息的标志,则此字段包含该其他信息(例如堆栈跟踪或上游服务的其他报告) |
debugUri |
String |
页面的URI,其中包含可能有助于调试错误的其他信息(这可能是此类错误的通用页面,或者是有关特定错误实例的特定页面) |
下表显示了可用ErrorType enum值:
类型 |
描述 |
HTTP模拟 |
BAD_REQUEST |
这表明请求有问题。重试相同的请求不太可能成功。可能是无法反序列化的查询或参数。 |
400错误的要求 |
FAILED_PRECONDITION |
由于系统未处于执行该操作所需的状态,因此该操作被拒绝。例如,要删除的目录为非空rmdir目录,对非目录执行操作,等等。UNAVAILABLE如果客户端可以重试失败的调用而无需等待系统状态被明确修复,则使用该目录代替。 |
400错误的请求,或500内部服务器错误 |
INTERNAL |
这表明遇到了意外的内部错误:基础系统预期的一些不变式已被破坏。该错误代码保留用于严重错误。 |
500内部服务器错误 |
NOT_FOUND |
这可能适用于从未存在的资源(例如,错误的资源ID)或不再存在的资源(例如,缓存已过期)。服务器开发人员注意:如果针对整个用户类别的请求被拒绝,例如逐步推出功能或未记录的允许列表,NOT_FOUND则可以使用。如果针对某类用户中的某些用户的请求被拒绝,则PERMISSION_DENIED必须使用基于用户的访问控制。 |
找不到404 |
PERMISSION_DENIED |
这表明请求者没有执行指定操作的权限。PERMISSION_DENIED不得用于由于耗尽一些资源或配额而导致的拒绝。PERMISSION_DENIED如果无法识别呼叫者,则不得使用(UNAUTHENTICATED代替用于这些错误)。此错误并不表示请求有效,或者所请求的实体存在或满足其他前提条件。 |
403禁止 |
UNAUTHENTICATED |
这表明请求没有有效的身份验证凭据,但路由需要身份验证。 |
401未经授权 |
UNAVAILABLE |
这表明该服务当前不可用。这很可能是暂时情况,可以通过重试重试来纠正。 |
503不可用 |
UNKNOWN |
例如,当从另一个地址空间接收的错误代码属于该地址空间中未知的错误空间时,可能会返回此错误。如果是API引发的错误,导致未返回足够的错误信息,也可能会转换为该错误。如果客户看到一个errorType未知的,它将被解释为UNKNOWN。未知错误不得触发任何特殊行为。他们可以通过等价于一个实现来处理INTERNAL。 |
520未知错误 |
##$!callback.setFileName($tool.append($!{tableInfo.name}, ".graphqls"))
##$!callback.setSavePath($tool.append($modulePath, "/src/main/resources/schema"))
##引入宏定义
$!define
##使用宏定义设置回调(保存位置与文件后缀)
#save("/schema", ".graphqls")
type Query {
$!{tableInfo.name}(${tableInfo.name}: String)): [${tableInfo.name}]
}
type ${tableInfo.name} {
#foreach($column in $tableInfo.fullColumn)
#if(${column.comment})
#${column.comment}
#end
$!{column.name}: $!{tool.getClsNameByFullName($column.type)}
#end
}