SpingBootで作るGraphQLアプリケーション

# 概要

SpringBootでGraphQLのサポートがされました！

ので、実際に触ってみたいと思います！

# そもそもGraphQLって？

おそらくRestAPIに親しんだ人が多いと思うので、RestAPIと比較をしてみます。

例えばToDoListのタスクの一覧を取得するとき
RestAPIだと `GET /todoList` みたいなエンドポイントにアクセスし情報を取得しますが、
GraphQLだとエンドポイントは１つしかなく、

```graphql
{
  todoList: {
    id, title
  }
}
```

このようにどんな情報が欲しいかクエリで指定して情報を取得します。

これだけだとRestAPIの方がクエリを書かなくて良い分便利に思えるのですが、
Webサービスを作ると「同じような情報を取得するけど、機能ごとに微妙に違う情報が必要」ということが出てきます。
このときRestAPIだと機能ごとにAPIを増やさなければなりませんが、GraphQLだとクエリさえ変えれば良いです！

# GraphQLで作るToDoリストアプリケーション

それでは具体的なGraphQLの実装方法に移っていきますが、
ここではToDoリストの情報を提供するようなアプリケーションを作りながら
GraphQLの実装ポイントをまとめてみます。

## Todoのリスト取得

まずはTODOをリストで取得するところまでやってみます。

### スキーマ定義

RestAPIでいう、IFの定義の部分です

`resources/graphql`配下に`schema.graphqls`を作成します。

```graphql:resources/graphql/schema.graphqls
type Query {
    todoList: [Todo]
}

type Todo {
    id: ID
    title: String
}
```

ポイント

- `type Query`では`todoList`を要求すると`Todo`のリストを返すよということを定義している
- `type Todo`では`Todo`というスキーマが保持する情報を定義している

※QueryでエンドポイントとそのレスポンスBodyを定義して、その他でレスポンスボディの詳細を定義してあげるイメージ

### Controller実装

RestAPIと同じように `@Controller`を付与したクラスを用意します。
RestAPIとの違いは、`GetMapping`等の代わりに`@QueryMapping`を使用するところです。

```java
@Controller
public class TodoController {
    @QueryMapping
    Flux<Todo> todoList() {
        return Flux.just(
                new Todo("1", "task1"),
                new Todo("2", "task2")
        );
    }
}
```

```java
@Value
public class Todo {
    private String id;
    private String title;
}
```

ポイント

- メソッド名は `type Query`で定義した名前と対応させる
- データクラスは名前とフィールドで保持する情報をスキーマ定義と対応させる

### 動かしてみる

application.ymlで

```yaml
spring:
  graphql:
    graphiql:
      enabled: true
```

の設定を追加してからbootRun !

> 本番アプリケーションではtrueにしないでね😇

http://localhost:8080/graphiql/ にアクセスして、以下のようなクエリを実行してみます。

```graphql
query {
  todoList {
    id,
    title
  }
}
```

![スクリーンショット 2022-05-29 18.52.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/361020/6994296d-f78b-7903-eb54-f47d0bbe887b.png)

情報取れましたね！！！

次はtitleだけ取得するようにしてみます。

```graphql
{
  todoList {
    title
  }
}
```

> queryは省略可

![スクリーンショット 2022-05-29 18.54.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/361020/38ad7ba5-544f-8077-413c-4361d83aa920.png)

クエリに合わせて取得できる情報がtitleだけになりました！

「サーバサイドの実装を変えずに必要な情報にしぼってレスポンスを変える」ことができました！！

## パラメータを渡す

idを指定して、合致するTodoだけを返せるようにしてみます。

### スキーマ定義

```
type Query {
    todo(id: String): Todo
}
```

ポイント

- パラメータは `(フィールド名: 型)`で指定する


### Controller実装

```java
@Controller
public class TodoController {
    private static List<Todo> TODO_LIST = Arrays.asList(
            new Todo("1", "task1"),
            new Todo("2", "task2")
    );

    @QueryMapping
    Todo todo(@Argument String id) {
        return TODO_LIST.stream()
                .filter(todo -> todo.getId().equals(id))
                .findFirst()
                .orElse(null);
    }
}
```

ポイント

- パラメータは `@Argument` を付与した引数で受け取る

### 動かしてみる

http://localhost:8080/graphiql/ にアクセスして、以下のようなクエリを実行してみます。

```graphql
{
  todo(id: "1")  {
    title
  }
}
```

![スクリーンショット 2022-07-20 8.03.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/361020/d3bd6388-0060-567a-65cf-bc78510c4d21.png)

## 複数のスキーマを結合する

次は複数のプロジェクトで、それぞれのプロジェクトにたくさんのTODOが状況を想定し、
プロジェクトを検索したら紐づくTODOも取得できるようにしてみます。

### スキーマ定義

```graphql
type Query {
    todoList: [Todo]
    projects: [Project]
    todo(id: String): Todo
}

type Todo {
    id: ID
    title: String
}

type Project {
    id: ID
    name: String
    todoList: [Todo]
}
```

### Controller実装

今回は簡略的に、TODOのタイトルの先頭にプロジェクト名があり、
前方一致でどのプロジェクトと紐づくのか判断できるようにしておきます。

スキーマ同士の紐付けを定義する場合は `@SchemaMapping` を使用します。
今回の例だと、
プロジェクトを検索条件にTODOのリストが取得できれば良いので、
「引数：Project、戻り値：Todoのリスト」
というメソッドを用意します。

```java
@Controller
public class TodoController {
    private static List<Todo> TODO_LIST = Arrays.asList(
            new Todo("1", "project1-task1"),
            new Todo("2", "project1-task2"),
            new Todo("3", "project1-task3"),
            new Todo("4", "project2-task1"),
            new Todo("5", "project2-task2"),
            new Todo("6", "project2-task3")
    );

    private static List<Project> PROJECT_LIST = Arrays.asList(
            new Project("1", "project1"),
            new Project("2", "project2")
    );

    @QueryMapping
    Flux<Project> projects() {
        return Flux.fromStream(PROJECT_LIST.stream());
    }

    @SchemaMapping(typeName = "Project")
    Flux<Todo> todoList(Project project) {
        return Flux.fromStream(
                TODO_LIST.stream()
                        // 前方一致で対応するTODOのみに絞り込み
                        .filter(todo -> todo.getTitle().startsWith(project.getName()))
        );
    }
}
```

ポイント

- `SchemaMapping`の`typeName`で引数で受け取るスキーマを設定する

![スクリーンショット 2022-07-20 8.25.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/361020/56f11cf7-e33a-ee65-57a6-b0f98063a445.png)

> もしTodoもProjectも同一のDBに入っているのであればSQLで結合させていっぺんに情報をとった方が絶対早い気がします。
自分が使うとしたらTodoとProjectが別のリソース（DBが別とか、片方がAPIからしか取得できないとか）の時の使用にとどめると思います。

## データの更新

次は更新系としてTodoの追加をやってみようと思います。
RestAPIだと情報をPOSTで受け取って処理するやつですね。

### スキーマ定義

更新系は`Mutation`というtypeで定義していきます。
書き方はパラメータを渡す時のスキーマ定義と似ています。

```graphql
type Mutation {
    addTodo(title: String): Todo
}
```

ポイント

- `addTodo`はString型の`title`を受け取って処理を行い、結果を`Todo`型で返すよということを定義している

### Controller実装

もともと定義している`TODO_LIST`に要素を追加するような処理を実装してみます。
※Arrays.asListだとImmutableなListが生成されるので`new ArrayList`でmutableなListに変換

Queryとの違いは、`@QueryMapping`の代わりに`@MutationMapping`を使用するところです。

```java
@Controller
public class TodoController {
    private static List<Todo> TODO_LIST = new ArrayList<>(Arrays.asList(
            new Todo("1", "project1-task1"),
            new Todo("2", "project1-task2"),
            new Todo("3", "project1-task3"),
            new Todo("4", "project2-task1"),
            new Todo("5", "project2-task2"),
            new Todo("6", "project2-task3")
    ));

    @MutationMapping
    Mono<Todo> addTodo(@Argument String title) {
        var id = UUID.randomUUID().toString();
        var todo = new Todo(id, title);
        TODO_LIST.add(todo);
        return Mono.just(todo);
    }
}
```

ポイント

- メソッド名は`type Mutation`で定義した名前と対応させる
- パラメータは`@Argument`を付与した引数で受け取る

### 動かしてみる

```graphql
mutation {
  addTodo(title: "task-0") {
    id
    title
  }
}
```

![スクリーンショット 2022-07-23 13.36.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/361020/6c91cd1b-3c63-2abf-7e2b-7625f9973d49.png)