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サーバを構築する
- SpringInitializr で以下を [Dependencies] に含めてソースを生成する。
- Spring Web
- Spring for GraphQL
- Lombok
- 生成したソースを IntelliJ で読み込む。
- 以下のファイルを追加する。
- 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のスキーマ定義を参照できるようになる。使い方は後述。
- サーバを起動する。
◆ GraphQLサーバからデータを取得する
以下URLにクエリを POST で送信すればデータを取得できます。
- GraphQLのURL:
http://localhost:8080/graphql
何かしらのツールを使って送信すればいいのですが、Spring for GraphQL では以下URLで GraphiQL(≠GraphQL)を使えるため、そちらで試してみます。
- GraphiQL(≠GraphQL)のURL:
http://localhost:8080/graphiql
Employee の情報だけほしい場合は、以下のようなクエリで取れます。
Department の情報もほしければ、以下のようなクエリで取れます。
code で絞り込みたい場合は、以下のようなクエリで取れます。
以下URLでGraphQLのスキーマ定義も参照できます。
- GraphQLのスキーマ定義URL:
http://localhost:8080/graphql/schema
◆ まとめ
SQLみたいな感覚で、ほしい情報だけデータ取得できました。
今回は仮データを返却する形にしましたが、実際は Employee は「Google API」からデータ取得、Department は「オンプレのPostgreSQL」からデータ取得する形で実装すれば、やりたかったことはできます。
ただ、Employee と Department の結合に関しては、スキーマ毎に結合処理を別途実装しないといけないため、理想的ではなかったです。。
例えば、新たなスキーマ Company と Department を結合したい場合、この2つの結合処理も別途実装する必要があります。
フレームワークがクエリをうまい具合に読み取って、各スキーマのデータ取得&結合してくれるのが理想的でした。。
結合処理については、REST API 使ったときみたいにコード上で結合処理書くのと大差ない気がするので、GraphQLを使うかどうかは、他のメリット・デメリットで判断したほうがよさそうです。
他のGraphQL(Apollo とか)であれば、結合も楽だったりするんですかね?
機会があれば、その辺も調べてみようと思います。