最近,GraphQL 在构建后端 API 方面获得越来越多大公司的青睐, 如PayPal、Facebook、Hasura、去哪儿等公司都做了大量实践。
它为客户端提供了一种灵活的方式来请求它需要的数据,提供严格类型的接口来查询数据,以及比 REST 更好的错误处理。
与 REST API 相比,GraphQL 有其自身的优势,例如
这些只是其中的一些优势。 今天本文将介绍如何在springboot项目中实践GrapQL API.
首先可以转到 https://start.spring.io 并创建一个具有以下依赖项的应用程序:
接下来为一个简单查询API 定义一个GraphQL模型。
type Person {
id: Int!
name: String!
address: [Address]
phone: String
}
type Address {
type: AddressType!
street: String
}
enum AddressType {
PRIMARY
SECONDARY
}
type Query {
person(id: Int!): Person
}
在这里,我们有一个类型Person
,该类型具有嵌套类型Address
.然后,我们提供了一个简单的查询API,用于根据id获取一个Person
对象数据。 !
表示将参数定义为必需属性。
接下来,让我们使用mutation
来添加编辑数据的功能。
input AddressInput {
id: String!
personId: Int!
type: AddressType!
street: String!
}
input PersonInput {
id: String!
name: String!
}
type Mutation {
createPerson(person: PersonInput): Person
createAddress(address: AddressInput): Address
}
在这里,我们将创建地址与创建人员分开。这只是为了简单起见, 也可以将人员的创建与地址相结合。为了修改数据,这里必须使用类型定义address
字段 为AddressInput
类型,这是因为mutation仅适用于输入类型。
定义模型后,让我们将其放在资源目录中的文件夹 graphql 下,文件名为 schema.graphqls 。Spring 会自动读取扩展名为 *.graphqlss 的模型文件。
接下来开始创建对应类。
为了简单起见,我们将直接在 GraphQL 接口中使用领域类。
@Entity
public class Person {
@Id
private Integer id;
private String name;
// Getters and setters omitted
}
@Entity
public class Address {
@Id
private Integer id;
private Integer personId;
private String street;
private AddressType type;
// Getters and setters omitted
}
public enum AddressType {
PRIMARY,
SECONDARY
}
最后,我们再实现两个存储库,如下所示。
public interface PersonRepository extends JpaRepository<Person, Integer> {
}
public interface AddressRepository extends JpaRepository<Address, Integer> {
List<Address> findByPersonId(Integer personId);
}
对于地址存储库,我们定义一个额外的方法findByPersonId
来获取与人员相关的地址。
完成上述工作后,让我们来实现 GraphQL API!
让我们定义用于查询数据的 API。
@Controller
public class GraphQLController {
@Autowired
private PersonRepository personRepository;
@Autowired
private AddressRepository addressRepository;
@QueryMapping(value = "person")
public Optional<Person> getPerson(@Argument(name = "id") Integer id) {
return personRepository.findById(id);
}
@SchemaMapping
public List<Address> address(Person person) {
return addressRepository.findByPersonId(person.getId());
}
在这里,我们定义了用于处理查询请求的 API。带有@QueryMapping
注释的函数将成为查询的处理程序。
查询名称会自动映射到函数名称,或者使用 @QueryMapping
接口的value
参数(在本例中为person
)显式定义它。
然后,我们使用指定参数名称@Argument
注释来定义输入。
@SchemaMapping
将成为查询嵌套字段的处理程序。映射基于函数名称本身(如上所示)或通过设置值参数(如下所示)。
@SchemaMapping(value = "address")
public List<Address> getAddress(Person person) {
System.out.println("Fetching address");
return addressRepository.findByPersonId(person.getId());
}
让我们看看如何定义修改操作。
@MutationMapping(name = "createPerson")
public Person addPerson(@Argument(name = "person") Person person) {
return personRepository.save(person);
}
@MutationMapping(name = "createAddress")
public Address addAddress(@Argument(name = "address") Address address) {
return addressRepository.save(address);
}
在这里,我们遵循与@QueryMapping
注释相同的原则。我们使用@MutationMapping
注释将修改操作名称映射到处理程序。
完成了这一步骤,我们就编写完成了所有处理程序。
为了调试 GraphQL 请求,Spring Boot GraphQL 提供了一个已经内置的 GraphiQL UI,我们可以使用它来测试我们的 API。
要启用它,我们需要设置以下属性。
spring:
graphql:
graphiql:
enabled: true
有了这个,就可以访问 /graphiql 路径上的 UI .它将自动扫描资源目录中的模型,以帮助开发人员验证 GraphQL 请求。
UI 提供了不错的功能,例如自动完成和模型文档,有助于大家轻松创建请求。
现在让我们启动应用程序并查询数据。
我们可以在路径 /graphiql 访问 GraphiQL 接口。
在查询数据之前,让我们先使用mutation操作存储一些数据。
mutation create_person {
createPerson(person: { id: 1, name: "amrut" }) {
id
name
}
}
mutation create_Address {
createAddress(address: { id: 1, personId: 1, type: SECONDARY, street: "some street" }) {
type
street
}
}
在这里,我们创建了两个修改请求,一个用于创建一个人,一个用于创建一个地址。 第一个操作创建一个人,只返回id和name,因为我们只对这两个属性感兴趣。同样,创建地址接口返回地址的类型和街道。
现在,让我们查询刚刚存储的数据。
query get_person {
person(id: 1) {
id
name
address {
type
street
}
}
}
我们看到了如何使用查询和修改操作使用 GraphQL 添加和查询数据。
GraphiQL 在内部对端点 /graphql 进行 POST 调用以发送查询。因此也可以使用 curl 命令执行此操作。
curl --location --request POST 'http://localhost:8080/graphql' \
--header 'Content-Type: application/json' \
--data-raw '{"query":"query get_person{ person(id : 1){ id name }}"}'
我们可以使用属性spring.graphql.path
更改此默认路径
接下来让我们看看如何处理错误。
首先看看当我们遇到错误时会发生什么。
为此,我们在调用获取电话字段时,抛出一个异常。
@SchemaMapping(value = "phone")
public String getPhone(Person person) {
throw new RuntimeException("Did not find phone data");
}
然后,让我们调用查询电话。
query get_person {
person(id: 1) {
id
name
phone
}
}
当我们发送请求后,我们会得到以下输出。
{
"errors": [
{
"message": "INTERNAL_ERROR for e7f58f69-e800-0622-1374-046bff96d0cb",
"locations": [
{
"line": 29,
"column": 7
}
],
"path": ["person", "phone"],
"extensions": {
"classification": "INTERNAL_ERROR"
}
}
],
"data": {
"person": {
"id": 1,
"name": "amrut",
"phone": null
}
}
}
errors 数组包含来自各种程序问题的错误列表。每个错误都有一个错误消息、一个路径和一个查询中发生错误的位置,指示哪个字段导致了错误。
出现错误时,默认程序返回上述值。
我们可以通过创建自己的错误处理程序解析器来自定义返回错误的方式。
@Component
public class ErrorHandlerResolver extends DataFetcherExceptionResolverAdapter {
@Override
protected List<GraphQLError> resolveToMultipleErrors(Throwable ex, DataFetchingEnvironment env) {
GraphQLError build = GraphqlErrorBuilder.newError(env)
.message(ex.getMessage())
.build();
return Arrays.asList(build);
}
}
我们新建了一个自定义错误处理程序,其中包含异常和对数据获取环境元数据的引用,我们可以使用它获取其他信息,例如发生错误的位置和路径。
有了这个处理程序,错误现在看起来像这样:
{
"errors": [
{
"message": "Did not find phone data",
"locations": [
{
"line": 29,
"column": 7
}
],
"path": ["person", "phone"],
"extensions": {
"classification": "DataFetchingException"
}
}
],
"data": {
"person": {
"id": 1,
"name": "amrut",
"phone": null
}
}
}
本文介绍了如何使用 GraphQL 来查询数据、更改数据以及定义自定义错误处理程序,更多知识整理在thisjava公众号中。
希望进一步了解GranphQL的同学可以在官网https://graphql.org/ 进一步了解更深入的知识。