跳到主要内容

GPI(GraphQL API)

定义 GraphQL 编程接口

GraphQL API 基于Microprofile GraphQL 协议实现, 接口使用注解和 GraphQL Entities 进行定义, 如果你使用过 Spring MVC 或是 Spring Boot, GPI 与 Controller 有一定的相似性. GPI 接口以编程的方式拓展 GraphQL

生成 GraphQL Entities

使用 Grphoenix 的 Gradle 插件可以根据 GraphQL 类型定义逐一生成对应的 Java Entities, API 接口可以使用生成的 Entities 定义接口方法的参数和返回值

引入 Gradle 插件

buildscript {
repositories {
jcenter()
}
}

plugins {
id 'java-library'
id "org.graphoenix" version "0.1.1"
}

使用 Gradle 插件生成 Java Entities

./gradlew :order-package:generateGraphQLSource

插件会在对应目录生成枚举, 输入类型和对象类型

|-- order-package                             订单包
|-- build.gradle
|-- src
|-- main
|-- java
|-- demo.gp.order
|-- dto
|-- enumType 枚举类型
|-- inputObjectType Input类型
|-- interfaceType 接口类型
|-- objectType Object类型

定义 GraphQL Entities

有时候 API 接口可能需要自定义的 Java Entities 作为方法的参数或返回值, 这些类型可能并没有定义在 GrpahQL 类型定义中, 此时可以使用 @Type @Input @Interface @Enum 类型注解分别来定义类型, 输入类型, 接口和枚举, 定义好的 Entities 会自动编译并添加到 GrahpQL 类型中作为补充

GraphQL 接口类

使用 @GraphQLApi 注解来定义接口类

  1. 新建接口类
|-- order-package                             订单包
|-- build.gradle
|-- src
|-- main
|-- java
|-- demo.gp.order
|-- api
|-- SystemApi.java 系统API
|-- dto
|-- annotation GPA注解
|-- directive 指令注解
|-- enumType 枚举类型
|-- inputObjectType Input类型
|-- interfaceType 接口类型
|-- objectType Object类型
  1. 定义接口类
package demo.gp.order.api;

import jakarta.enterprise.context.ApplicationScoped;
import org.eclipse.microprofile.graphql.GraphQLApi;

@GraphQLApi // 使用@GraphQLApi 注解标记接口所在 CDI Bean
@ApplicationScoped
public class SystemApi {
// 定义接口...
}

查询接口

使用 @Query 注解来定义查询接口, 接口会添加到查询类型中

普通查询接口

定义一个简单接口, 传入 userName, 返回欢迎和系统时间, 使用 @Query 注解标记接口方法

package demo.gp.order.api;

import jakarta.enterprise.context.ApplicationScoped;
import org.eclipse.microprofile.graphql.GraphQLApi;
import org.eclipse.microprofile.graphql.Query;

import java.time.LocalDateTime;

@GraphQLApi
@ApplicationScoped
public class SystemApi {

@Query
public String hello(String userName) {
return "Hello " + userName + ", The time is now " + LocalDateTime.now();
}
}

查询 hello 接口

{
hello(userName: "Gosling")
}
{
"data": {
"hello": "Hello Gosling, The time is now 2024-05-30T11:51:41.164692"
}
}

异步查询接口

接口支持响应式类型的返回值 Mono 和 Flux

例: 定义一个异步接口, 传入 userName, 异步返回欢迎和系统时间

@GraphQLApi
@ApplicationScoped
public class SystemApi {

// ...省略其他接口

@Query
public Mono<String> helloAsync(String userName) {
return Mono.just(LocalDateTime.now())
.map(now -> "Hello " + userName + ", The time is now " + now);
}
}

查询 helloAsync 接口

{
helloAsync(userName: "Gosling")
}
{
"data": {
"helloAsync": "Hello Gosling, The time is now 2024-05-31T16:18:39.851451"
}
}

变更接口

使用 @Mutation 注解来定义变更接口, 接口会添加到变更类型中

普通变更接口

例: 定义一个用户注册 register 接口, 使用 @Mutation 注解标记接口方法

@GraphQLApi
@ApplicationScoped
public class SystemApi {

// ...省略其他方法

@Mutation
public RegisterResult register(RegisterInput registerInput) {
String account = registerInput.getEmail().substring(0, registerInput.getEmail().indexOf("@"));
Integer age = Period.between(registerInput.getBirthday(), LocalDate.now()).getYears();

RegisterResult registerResult = new RegisterResult();
registerResult.setAccount(account);
registerResult.setPassword(UUID.randomUUID().toString());
registerResult.setAge(age);

return registerResult;
}
}

调用 register 接口

mutation {
register(
registerInput: {
name: "Gosling"
email: "gosling@java.com"
birthday: "1955-05-19"
}
) {
account
password
age
}
}
{
"data": {
"register": {
"account": "gosling",
"password": "vuvvtrachs",
"age": 69
}
}
}

异步变更接口

和查询一样, 变更接口同样支持异步返回值

定义一个异步变更接口, 返回 Flux

@GraphQLApi
@ApplicationScoped
public class SystemApi {

// ...省略其他接口

@Mutation
public Flux<String> countingSheep(int count) {
return Flux.range(0, count)
.map(index -> index + 1 + " sheep");
}
}

查询 countingSheep 接口

mutation {
countingSheep(count: 3)
}

Flux 的元素会聚合成数组后返回

{
"data": {
"countingSheep": ["1 sheep", "2 sheep", "3 sheep"]
}
}

字段接口

在有些场景下, 需要在数据库返回后对结果进行加工, 并产生新的字段, 如数学计算和调用规则引擎等

使用 @Source 注解在方法参数中标记带有 @Type 注解的 Entity, 每一次对接口字段的请求都会调用对应的方法来获取返回值

例: 计算每一个订单的价格合计

@GraphQLApi
@ApplicationScoped
public class SystemApi {

// ...省略其他接口

public Float total(@Source Order order) {
if (order.getItems() != null) {
return order.getItems().stream()
.filter(orderItem -> orderItem.getProduct() != null && orderItem.getProduct().getPrice() != null)
.map(orderItem -> orderItem.getProduct().getPrice() * orderItem.getQuantity())
.reduce(Float::sum)
.orElse(null);
}
return null;
}
}

此时 Order 对象会生成一个新的名为 total 的字段

type Order implements Meta {
"订单ID"
id: ID!
"购买用户"
user: User!
"产品列表"
items: [OrderItem!]!
total: Float
}

查询用户 Diana 的订单

{
user(name: {opr: EQ, val: "Diana"}) {
name
orders {
items {
product {
name
price
}
quantity
}
total
}
}
}

订单会在 api 接口中计算后返回结果

{
"data": {
"user": {
"name": "Diana",
"orders": [
{
"items": [
{
"product": {
"name": "Laptop",
"price": 999.99
},
"quantity": 1
},
{
"product": {
"name": "Phone",
"price": 499.99
},
"quantity": 1
},
{
"product": {
"name": "Tablet",
"price": 299.99
},
"quantity": 1
}
],
"total": 1799.97
}
]
}
}
}

参数接口

在有些场景下, 需要在数据库查询和变更前对查询条件或提交内容进行加工, 如校验, 鉴权或修改等

使用 @Source 注解在接口参数中标记带有 @Input 注解的 Bean, 接口的返回值将会覆盖原始参数

例: 在后台增加条件, 隐藏用户 Mike

@GraphQLApi
@ApplicationScoped
public class SystemApi {

// ...省略其他接口

public UserListQueryArguments hideMike(@Source UserListQueryArguments userListQueryArguments) {
if (userListQueryArguments == null) {
userListQueryArguments = new UserListQueryArguments();
}
StringExpression stringExpression = new StringExpression();
stringExpression.setOpr(Operator.NEQ);
stringExpression.setVal("Mike");
userListQueryArguments.setName(stringExpression);
return userListQueryArguments;
}
}

查询用户列表

{
userList {
name
}
}

用户列表中没有 Mike

{
"data": {
"userList": [
{
"name": "Alice"
},
{
"name": "Bob"
},
{
"name": "Charlie"
},
{
"name": "Diana"
},
{
"name": "Edward"
},
{
"name": "Fiona"
},
{
"name": "George"
},
{
"name": "Hannah"
},
{
"name": "Ian"
},
{
"name": "Jane"
},
{
"name": "Kyle"
},
{
"name": "Laura"
},
{
"name": "Nina"
},
{
"name": "Oliver"
},
{
"name": "Paula"
},
{
"name": "Quentin"
},
{
"name": "Rachel"
},
{
"name": "Steve"
},
{
"name": "Tina"
}
]
}
}

注解说明

类注解

注解说明示例GraphQL 类型
@GraphQLApi定义 GraphQL 接口类, 接口类中可以定义
查询接口, 变更接口, 字段接口和参数接口
@Input定义 GraphQL 输入类型@Input("StarshipInput")
public class Starship {
 private String id;
 private String name;
 private float length;
 // getters/setters...
}
input StarshipInput {
 id: String
 name: String
 length: Float
}
@Type定义 GraphQL 类型@Type("Starship")
public class Starship {
 private String id;
 private String name;
 private float length;
 // getters/setters...
}
type Starship {
 id: String
 name: String
 length: Float
}
@Input定义 GraphQL 输入类型@Input("StarshipInput")
public class Starship {
 private String id;
 private String name;
 private float length;
 // getters/setters...
}
input StarshipInput {
 id: String
 name: String
 length: Float
}
@Interface定义 GraphQL 接口类型@Interface("Aircraft")
public interface IAircraft {
 private String getName();
}
interface Aircraft {
 name: String
}
@Enum定义 GraphQL 枚举类型@Enum("ClothingSize")
public enum ShirtSize {
 S,
 M,
 L,
 XL,
 XXL
}
enum ClothingSize {
 S
 M
 L
 XL
 XXL
}

字段注解

注解说明示例GraphQL 类型
@Id定义 ID 类型@Type
public class Person {
 @Id
 private String id;
 private String name;
 // getters/setters...
}
type Person {
 id: ID
 name: String
}
@NonNull定义非空类型@Input
public class StarshipInput {
 @NonNull
 private String name;
 private Float length;
 // getters/setters...
}
input StarshipInput {
 name: String!
 length: Float
}

接口注解

注解说明示例GraphQL 接口
@Query定义查询接口@Query("friendsOf")
public List<Character> getFriendsOf(Character character) {
 // return ...
}
type Query {
 friendsOf(character: CharacterInput): [Character]
}
@Mutation定义变更接口@Mutation("addCharacter")
public Character save(Character character) {
 // return ...
}
type Mutation {
 addCharacter(character: CharacterInput): Character
}
@Source定义字段接口或参数接口public List <Tweet> tweets(@Source("tweetsForMe") Character character) {
 // return ...
}
type Character {
 # Other fields ...
 tweetsForMe(last: Int): [Tweet]
}

其他注解

注解说明示例GraphQL 接口
@DefaultValue定义默认值public List <Character> getByName(@DefaultValue("Han Solo") String name) {
 // ...
}
type Query {
 searchByName(name: String = "Han Solo"): [Character]
}
@Description定义注释@Description("Vehicle for traveling between star systems")
public class Starship {
 private String id;
 private float length;
 @Description("Name of a particular starship")
 private String name;
}
"Vehicle for traveling between star systems"
type Starship {
 id: String
 length: Float
 "Name of a particular starship"
 name: String
}

返回值说明

方法返回类型GraphQL 类型说明示例 (Type=User)GraphQL 接口
(Type)(Type)同步查询User queryUser(String name) {
 return ...
}
queryUser(name: String): User
Mono<(Type)>(Type)异步查询Mono<User> queryUser(String name) {
 return ...
}
queryUser(name: String): User
Flux<(Type)>[(Type)]异步查询, 聚合为数组后返回Flux<User> queryUserList(String name) {
 return ...
}
queryUserList(name: String): [User]

本节示例

https://github.com/doukai/order/tree/main/order-package/src/main/java/demo/gp/order/api