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
注解来定义接口类
- 新建接口类
|-- order-package 订单包
|-- build.gradle
|-- src
|-- main
|-- java
|-- demo.gp.order
|-- api
|-- SystemApi.java 系统API
|-- dto
|-- annotation GPA注解
|-- directive 指令注解
|-- enumType 枚举类型
|-- inputObjectType Input类型
|-- interfaceType 接口类型
|-- objectType Object类型
- 定义接口类
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"
}
}
异步查询接口
例: 定义一个异步接口, 传入 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