<mark>此插件，主要社区贡献人（fuzi1996）</mark>

```xml
<dependency>
    <groupId>org.noear</groupId>
    <artifactId>graphql-solon-plugin</artifactId>
</dependency>
```

### 1. 描述

数据扩展插件，为 Solon Data 提供基于 GraphQL-Java 的框架适配，主要提供一些注解方便使用。

#### 注解

| 注解                 | 用途                   |
| -------------------- | ---------------------- |
| @QueryMapping        | 查询、修改             |
| @BatchMapping        | 批量查询               |
| @SchemaMapping       | 结构映射               |
| @SubscriptionMapping | 订阅，实时获取数据更新 |

除了 `@SubscriptionMapping` 外的注解都有三个字段：value、field、typeName

- value 和 field 等效：表示在 GraphQL 中绑定的字段名称，未指定指定 value 或 field 则默认为方法名

- typeName： 表示 GraphQL 字段的 source/parent 类型的名称，未指定则由注入的参数类名派生

`@SubscriptionMapping` 注解三个字段名称为：value、name、typeName，含义类似。

#### HTTP接口

对外提供三个 http 接口：

| 请求路径  | 请求方式 | 作用                                            |
| --------- | -------- | ----------------------------------------------- |
| /graphql  | POST     | 接收 GraphQL 查询，返回数据                     |
| /schema   | POST     | 获取所有 schema 结构，无需请求体                |
| /graphiql | GET      | GraphQL 交互式网页，常用于测试 GraphQL 语句编写 |

### 2. 配置示例

```yaml
server.port: 8081
```

无特殊配置，只需指定 GraphQL 服务端口即可。

### 3. 应用示例

代码均来自插件测试用例，可参见 [**这里**](https://github.com/noear/solon-integration/tree/main/solon-integration-projects/solon-plugin/graphql-solon-plugin/src/test) 获取完整示例。

在 `classpath:graphql` 目录下（如 `resources/graphql`）添加 graphqls 或者 gqls 后缀的文件，用以定义 GraphQL 对象类型。

在 `resources/graphql` 中添加 `base.gqls` 用于定义基础操作对象

```
type Query {
}

type Mutation {
}

type Subscription {
}
```

然后在其他 gqls 文件中使用 `extend` ，在基础操作对象中添加操作表明该操作的类型，具体可参见下文。

#### @QueryMapping

首先需要定义查询对象

```
extend type Query {
    bookById(id: ID): Book
}

type Book {
    id: ID
    name: String
    pageCount: Int
    author: Author
}
```

`extend type Query` 添加一个操作名称为 `bookById` 的操作到 `Query` 对象，表明该操作操作类型为 Query，而后根据参数 id 查询数据。

在 Java 代码中使用 `@QueryMapping` 注解：

```java
@QueryMapping
public BookInputDTO bookById(@Param String id) {
    return this.generateNewOne(id);
}
```

操作类型 typeName 默认为 Query（`@QueryMapping` 中默认），操作名称默认为方法名 `bookById` 。**在GraphQL 定义的参数名称（`bookById(id: ID): Book` 中的 `id` ）和注解修饰方法的入参（`@Param String id` 中的`id`）名称需要保持一致。**

而后就可以向指定地址（如 `http://localhost:8081/graphql` ）发送 GraphQL 查询语句，在请求体中以 JSON 格式发送，使用 `query` 和 `variables` 字段分别指定 GraphQL 查询语句和参数，就可以发送查询请求了（还可以指定 `operationName`）。请求和相应结果可参考 [GraphQL官网](https://graphql.cn/learn/serving-over-http/)。

可将 `query` 字段置为：

```
query ($id: ID){
    bookById(id: $id){
        id
        name
        pageCount
        author {
            firstName
            lastName
        }
    }
}
```

将 `variables` 字段指定为：

```json
{
    "id": "book-1"
}
```

**variables 中的字段名称 `id` 需要和 query 中的变量名称 `$id` 保持一致**

#### @BatchMapping

批量查询分两种：一对一批量查询，如一个课程对应一个讲师；一对多批量查询，如一个课程对应多个学生。

**一对一**

定义查询对象

```
extend type Query {
    courses: [Course]
}
type Course {
    id: ID
    name: String
    instructor: Person
    students: [Person]
}
type Person {
    id: ID
    firstName: String
    lastName: String
}
```

使用 `@BatchMapping` 注解

```java
@QueryMapping
public Collection<Course> courses() {
	return CourseSupport.courseMap.values();
}

@BatchMapping
public List<Person> instructor(List<Course> courses) {
	return courses.stream().map(Course::instructor).collect(Collectors.toList());
}
```

发送 GraphQL 查询请求

```
query {
    courses {
        id
        name
        instructor {
            id
            firstName
            lastName
        }
    }
}
```

其中 `courses ` 首先调用上述 `@QueryMapping` 修饰的 `public Collection<Course> courses()` 查询，查到多个课程。而后 `instrucor` 又调用 `@BatchMapping` 修饰的 `public List<Person> instructor(List<Course> courses)` 将每个课程的讲师信息填入。*这就是 GraphQL 带来的好处之一，不用手动一个个调用接口然后自己组装数据*。

**一对多**

查询对象在一对一中已定义，使用 `@BatchMapping` 注解

```java
@BatchMapping
public List<List<Person>> students(List<Course> courses) {
    return courses.stream().map(Course::students).collect(Collectors.toList());
}
```

发送 GraphQL 查询请求：

```
query {
    courses {
        id
        name
        students {
            id
            firstName
            lastName
        }
    }
}
```

整体和一对一类似，不同之处在于 courses 查询中的 students 结果为数组，会获取多个学生信息。

#### @SchemaMapping

用于结构映射，指定 typeName 为 source/parent 的类型，指定 field 绑定 GraphQL 字段，当查询该字段时，会调用该注解修饰的方法，同时根据 typeName 获取到父类型数据。如：

```java
@SchemaMapping(field = "author", typeName = "Book")
public AuthorInputDTO authorByBookId(BookInputDTO book) {
    return this.getDefaultAuthor();
}
```

指明绑定 `author` 字段，父类型名称为 `Book`

当收到如下查询时：

```
query ($id: ID){
    bookById(id: $id){
        id
        name
        pageCount
        author {
            firstName
            lastName
        }
    }
}
```

首先根据 bookById 查询数据，遇到 `author` 时调用上述 `@SchemaMapping(field = "author", typeName = "Book")` 修饰的方法，获取父类数据到 `BookInputDTO book` 而后根据 book 查询作者（逻辑上是这样，代码中没有根据 book 的数据获取作者）。 

#### @SubscriptionMapping

定义 `notifyProductPriceChange` 订阅类型

```
extend type Subscription {
    notifyProductPriceChange (productId: ID): ProductPriceHistory
}

type ProductPriceHistory {
    id: ID!
    price: Int!
    startDate: String!
}
```

其次使用 `@SubscriptionMapping` 注解订阅

```java
@SubscriptionMapping("notifyProductPriceChange")
public Flux<ProductPriceHistoryDTO> notifyProductPriceChange(@Param Long productId) {

    // A flux is the publisher of data
    return Flux.fromStream(
        Stream.generate(() -> {

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            return new ProductPriceHistoryDTO(productId, new Date(),
                                              (int) (rn.nextInt(10) + 1 + productId));
        }));

}
```

返回 Flux 对象可用于订阅事件然后解析做相关处理。
