このブログを検索

この記事の内容は、個人の見解、検証の範囲のものであり、誤りがある可能性があります。
個人の責任において情報活用をお願いします。


2023年3月30日木曜日

GraphQLに触れてみる

GraphQLに触れてみる

◆ やること

Webアプリで別リソースからデータ取得しないといけないケースがたまにあります。
例えば、人事情報は「Google API」から取得、その他は「オンプレのPostgreSQL」から取得…といったケースです。

GraphQLはこういったケースを想定してデータ取得できるようなので、実際にどんなものか触れてみようと思います。
また、別リソースを結合(SQLの内部結合みたいに)してデータ取得できると便利そうなので、結合できるかどうかも確認してみます。

Spring for GraphQL が2022年5月頃にリリースされたため、そちらを使います。

◆ 使うもの

  • Spring for GraphQL (v1.1.3)
  • IntelliJ (CE版 2022.3.1)
  • Java (v17)

◆ GraphQLサーバを構築する

  1. SpringInitializr で以下を [Dependencies] に含めてソースを生成する。
    • Spring Web
    • Spring for GraphQL
    • Lombok

  2. 生成したソースを IntelliJ で読み込む。

  3. 以下のファイルを追加する。
    • src/main/resources/graphql/schema.graphqls
      type Employee {
      code: ID!
      fullName: String!
      department: Department!
      }

      type Department {
      code: ID!
      name: String!
      }

      type Query {
      employees(code: String): [Employee]
      }
      • GraphQLのスキーマやクエリを定義するファイル。
      • employees クエリを呼び出すと Employee(従業員) と 所属している Department(部門) を取得できる。
      • employees クエリは code で絞り込みできる。実際に絞り込んでるのは後述の EmployeeController。

    • Employee.java
      @Getter
      @Builder
      @AllArgsConstructor
      @ToString
      @EqualsAndHashCode
      public class Employee {
      private final String code;
      private final String fullName;
      private final String departmentCode;
      }
      • Employee の DTO。
      • department のメンバはなく、代わりに departmentCode を持つ。
      • Department (DTO) との関連付けは Spring for GraphQL がやってくれる。ただし、後述の EmployeeController の @BatchMapping 関数で結合処理の記述が必要。

    • Department.java
      @Getter
      @Builder
      @AllArgsConstructor
      @ToString
      @EqualsAndHashCode
      public class Department {
      private final String code;
      private final String name;
      }
      • Department の DTO。

    • EmployeeController.java
      @Slf4j
      @Controller
      public class EmployeeController {

      @QueryMapping(name = "employees") //
      public List<Employee> employees(@Argument String code) {
      // 仮データを作成する
      List<Employee> employees = new ArrayList<>();
      employees.add(new Employee("E001", "山田 一郎", "D001"));
      employees.add(new Employee("E002", "山田 二郎", "D002"));
      employees.add(new Employee("E003", "山田 三郎", "D001"));

      // code が指定されている場合、Employee を絞り込む
      if (code != null && !code.equals("")) {
      employees = employees.stream()
      .filter(e -> code.equals(e.getCode()))
      .collect(Collectors.toList());
      }

      // 返却する
      return employees;
      }

      @BatchMapping(typeName = "Employee", field = "department") //
      public Map<Employee, Optional<Department>> department(
      List<Employee> employees) {
      // 仮データを作成する
      List<Department> departments = new ArrayList<>();
      departments.add(new Department("D001", "開発1課"));
      departments.add(new Department("D002", "開発2課"));

      // Employee Department のマッピングを生成して返却する
      return employees.stream()
      .collect(Collectors.toMap(e -> e, e -> departments.stream()
      .filter(d -> d.getCode().equals(e.getDepartmentCode()))
      .findFirst()));
      }
      }
      • employees クエリ用のコントローラ。
      • ①:employees クエリ呼出し時の入り口になる。
      • ②:employees クエリの呼出し時に Employee の department フィールドのデータを取得する際に呼び出される。パラメータの  employees は ① の結果が渡されるので、① の結果の departmentCode を元にデータを取得する形になる。 
        返却値は List を返却してもいいが、Map であればNULL値の設定も可能なので、Map を返却するのが無難。

    • application.yml
      server:
      port : 8080 #
      spring:
      graphql:
      graphiql:
      enabled: true #
      schema:
      printer:
      enabled: true #
      • SpringBoot お馴染みの設定ファイル。
      • ①:サーバのポートを 8080 にしている。
      • ②:true にすると、ブラウザでGraphiQL(≠GraphQL)が使えるようになる。使い方は後述。
      • ③:true にすると、ブラウザでGraphQLのスキーマ定義を参照できるようになる。使い方は後述。

  4. サーバを起動する。


◆ GraphQLサーバからデータを取得する

以下URLにクエリを POST で送信すればデータを取得できます。


何かしらのツールを使って送信すればいいのですが、Spring for GraphQL では以下URLで GraphiQL(≠GraphQL)を使えるため、そちらで試してみます。


Employee の情報だけほしい場合は、以下のようなクエリで取れます。
  • クエリ:
    query {
    employees {
    code
    fullName
    }
    }
  • 結果:



Department の情報もほしければ、以下のようなクエリで取れます。
  • クエリ:
    query {
    employees {
    code
    fullName
    department {
    code
    name
    }
    }
    }
  • 結果:


code で絞り込みたい場合は、以下のようなクエリで取れます。
  • クエリ:
    query {
    employees(code: "E002") {
    code
    fullName
    department {
    code
    name
    }
    }
    }
  • 結果:


以下URLでGraphQLのスキーマ定義も参照できます。

◆ まとめ

SQLみたいな感覚で、ほしい情報だけデータ取得できました。

今回は仮データを返却する形にしましたが、実際は Employee は「Google API」からデータ取得、Department は「オンプレのPostgreSQL」からデータ取得する形で実装すれば、やりたかったことはできます。
 
ただ、Employee と Department の結合に関しては、スキーマ毎に結合処理を別途実装しないといけないため、理想的ではなかったです。。
例えば、新たなスキーマ Company と Department を結合したい場合、この2つの結合処理も別途実装する必要があります。
フレームワークがクエリをうまい具合に読み取って、各スキーマのデータ取得&結合してくれるのが理想的でした。。

結合処理については、REST API 使ったときみたいにコード上で結合処理書くのと大差ない気がするので、GraphQLを使うかどうかは、他のメリット・デメリットで判断したほうがよさそうです。

他のGraphQL(Apollo とか)であれば、結合も楽だったりするんですかね?
機会があれば、その辺も調べてみようと思います。