LoginSignup
0
0

More than 1 year has passed since last update.

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

Posted at

概要

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

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

そもそもGraphQLって?

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

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

{
  todoList: {
    id, title
  }
}

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

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

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

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

Todoのリスト取得

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

スキーマ定義

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

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

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を使用するところです。

@Controller
public class TodoController {
    @QueryMapping
    Flux<Todo> todoList() {
        return Flux.just(
                new Todo("1", "task1"),
                new Todo("2", "task2")
        );
    }
}
@Value
public class Todo {
    private String id;
    private String title;
}

ポイント

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

動かしてみる

application.ymlで

spring:
  graphql:
    graphiql:
      enabled: true

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

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

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

query {
  todoList {
    id,
    title
  }
}

スクリーンショット 2022-05-29 18.52.06.png

情報取れましたね!!!

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

{
  todoList {
    title
  }
}

queryは省略可

スクリーンショット 2022-05-29 18.54.32.png

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

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

パラメータを渡す

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

スキーマ定義

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

ポイント

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

Controller実装

@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/ にアクセスして、以下のようなクエリを実行してみます。

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

スクリーンショット 2022-07-20 8.03.23.png

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

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

スキーマ定義

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のリスト」
というメソッドを用意します。

@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()))
        );
    }
}

ポイント

  • SchemaMappingtypeNameで引数で受け取るスキーマを設定する

スクリーンショット 2022-07-20 8.25.36.png

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

データの更新

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

スキーマ定義

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

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

ポイント

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

Controller実装

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

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

@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を付与した引数で受け取る

動かしてみる

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

スクリーンショット 2022-07-23 13.36.07.png

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0