23
23

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Spring BootでGraphQLを試した

Last updated at Posted at 2019-10-13

はじめに

遅ればせながらGraphQLなるものを知ったので、自分が使い慣れているJava+Spring Bootでの実装を試した。
GraphQL自体の説明はこちらなどに任せるとして以下では実装について記述する。

プロジェクト準備

開発環境

  • Windows 10
  • Java 11 (AdoptOpenJDK jdk11.0.4+11 OpenJ9 0.15.1)
  • Maven 3.5.4
  • STS 4.4.0

ベースとなるSpring Bootプロジェクトを作成する

  • Spring Boot 2.1.9
依存関係 備考
Spring Web 必須 サーブレットとして起動するため
Lombok 推奨 エンティティクラスを作成する上で便利なので
JDBC API 選択 DBアクセスの方法はお好みで
H2 Database
MyBatis Framework

GraphQL Javaの依存関係を追加する

graphql-java-kickstart/graphql-spring-bootを利用する。実行するための最小限の依存関係は以下のみでOK。

pom.xml
<dependency>
    <groupId>com.graphql-java-kickstart</groupId>
    <artifactId>graphql-spring-boot-starter</artifactId>
    <version>5.10.0</version>
</dependency>

プロパティとしてkotlinのバージョン指定も必要(記述がないと実行時に例外が発生する)。

pom.xml
<properties>
 	<java.version>11</java.version>
	<kotlin.version>1.3.10</kotlin.version>
</properties>

APIその1:簡単な取得クエリに対応

以下のようなモデルに対してIDにより1つのエンティティを取得することを考える。

メンバー(メンバーID, 氏名, 年齢)

GraphQL定義作成

以下のように型定義とクエリ定義を記述したファイルをsrc/main/resources/graphql配下に作成する。

schema.graphqls
type Member {
	memberId: ID!
	name: String!
	age: Int
}

type Query {
	getMember(memberId:ID!): Member
}

Java側の実装

エンティティクラス

まず型定義に合ったエンティティクラスを作成する。

Member
@Data
public class Member {
	private String memberId;
	private String name;
	private Integer age;
}
クエリ

次にクエリに対応するメソッドを作成する。GraphQLQueryResolver実装のクラスを作成し、クエリ名と同じ名前のメソッドを作成する。引数および戻り値も対応するクラスを定義し、実際に戻すインスタンスを実装する。戻り値の作成方法は任意(MyBatisのMapperを利用(割愛))。戻り値のフィールドにはサーブレットとして返す可能性のある値をすべて詰めておく

MemberQueryResolver
@Component
public class MemberQueryResolver implements GraphQLQueryResolver {

	@Autowired
	MemberMapper mapper;

	public Member getMember(String memberId) {
		return mapper.getById(memberId);
	}
}

実行

起動構成

サーブレットの起動構成をapplication.ymlに記述する。とりあえず以下の設定があれば起動する。詳細はこちらを参照

application.yml
graphql:
  servlet:
    enabled: true
    mapping: /graphql
    corsEnabled: true
起動

記述したら作成したプロジェクトをSpring Boot Appとして実行する。

クエリ発行

http://localhost:8080/graphqlに対してURLパラメータqueryの値としてGraphQLのクエリを設定してアクセスすると結果が得られる。

たとえば以下のクエリを発行する場合、

query {
  getMember(memberId:"aaaaaaaa") {
    name
  }
}

不要な空白と改行を除いてencodeURIするとquery%7BgetMember(memberId:%22aaaaaaaa%22)%7Bname%7D%7Dなのでcurlで確認する。

$ curl -s "http://localhost:8080/graphql?query=query%7BgetMember(memberId:%22aaaaaaaa%22)%7Bname%7D%7D"
{"data":{"getMember":{"name":"齊藤京子"}}}

memberIdを変更して、取得キーにageを加えた場合は以下のような結果が得られる。

$ curl -s "http://localhost:8080/graphql?query=query%7BgetMember(memberId:%22bbbbbbbb%22)%7Bname,age%7D%7D"
{"data":{"getMember":{"name":"佐々木久美","age":23}}}

GraphiQLを導入する

クエリを逐一encodeURIしてcurlやブラウザでアクセスするのは面倒すぎるので、クエリを試すツールを導入する。今回はGraphiQLを利用する。

依存関係の追加

graph「i」ql-spring-boot-starterを依存関係に追加する。

pom.xml
<dependency>
	<groupId>com.graphql-java-kickstart</groupId>
	<artifactId>graphiql-spring-boot-starter</artifactId>
	<version>5.10.0</version>
</dependency>

実行

起動構成

サーブレットの起動構成をapplication.ymlに記述する。とりあえず以下の設定があれば起動する。詳細はこちらを参照。上で記述したgraphqlの設定は不要。

application.yml
graphiql:
  enabled: true
  mapping: /graphiql
  endpoint:
    graphql: /graphql
起動

記述したら作成したプロジェクトをSpring Boot Appとして実行する。
サーバが起動したらブラウザからhttp://localhost:8080/graphiqlにアクセスする。

GraphiQL.PNG

左側のクエリ作成画面ではフィールド名の入力補助があり、実行結果のjsonも整形済みで表示される。

APIその2:型を入れ子にしてみる

その1でのモデルに加えて以下のような多対多のモデルをリレーションによってつないでいるモデルに対してIDにより1つのエンティティを取得することを考える。

メンバー(メンバーID, 氏名, 年齢)
楽曲(楽曲ID, 曲名)
参加楽曲リレーション(楽曲ID, メンバーID

GraphQL定義修正

定義ファイルに追記する。多対多なのでMemberに対するMusicMusicに対するMemberも配列になる。

schema.graphqls
type Member {
	memberId: ID!
	name: String!
	age: Int
	joining: [Music]
}

type Music {
	musicId: ID!
	title: String!
	members: [Member]
}

type Query {
	getMember(memberId:ID!): Member
	getMusic(musicId:ID!):Music
}

Java側の実装

エンティティクラス

新しい型定義に対応するエンティティクラスを作成する。配列として定義されているプロパティはListにしておく。

Music
@Data
public class Music {
	private String musicId;
	private String title;
	private List<Member> members;
}

Memberもプロパティが追加されたのでフィールドを追加する。

Member
@Data
public class Member {
	private String memberId;
	private String name;
	private int age;
	private List<Music> joining;
}
クエリ

MemberQueryResolver変更無しMember.joiningに値を挿入するロジックは別クラスに作成する。
DataClass(<>の中)には挿入先のエンティティクラスを指定したGraphQLResolver実装のクラスを作成し、挿入先フィールドと同名のメソッドを作成する。引数には挿入先のエンティティクラスを指定し、挿入するインスタンスをreturnする。
(MusicMapper.getMusicsOfMemberはmemberIdにリレーション経由で紐づくするMusicをすべて取得するメソッド(実装内容は割愛)。)

MemberResolver
@Component
public class MemberResolver implements GraphQLResolver<Member> {

	@Autowired
	MusicMapper mapper;

	public List<Music> joining(Member member) {
		return mapper.getMusicsOfMember(member.getMemberId());
	}
}

実行

以下のクエリを発行すると

query {
  getMember(memberId:"cccccccc") {
    name, age, joining {
      title
    }
  }
}

以下のようなデータが返却される。

{
  "data": {
    "getMember": {
      "name": "富田鈴花",
      "age": 18,
      "joining": [
        {
          "title": "こんなに好きになっちゃっていいの?"
        },
        {
          "title": "ホントの時間"
        },
        {
          "title": "まさか 偶然…"
        },
        {
          "title": "川は流れる"
        }
      ]
    }
  }
}

STSのデバッグモードで確認するとMemberResolver.joiningの引数にはjoining以外のフィールドに検索結果が挿入された状態のインスタンスが渡されることがわかる。
また、クエリにjoiningを指定していない場合および存在しないmemberIdを指定して結果がnullの場合はこのメソッドは実行されない。

APIその3:登録機能を作ってみる

ここまではQuery(CRUDのRに相当)のみを扱ってきた。次はMutation(CRUDのC・U・Dに相当)を実装してみる。

GraphQL定義追加

定義ファイルに追記する。

schema.graphqls
type Mutation {
	registerMusic(title: String!): Music
}

Java側の実装

ミューテーション

エンティティは既存のものを使用するので、ここではミューテーションに対応するメソッドのみを作成する。GraphQLMutationResolver実装のクラスを作成し、クエリと同様にGraphQLに対応する引数・戻り値を持つメソッドを作成する。
(generateId()はIDとなる文字列を生成するメソッド、MusicMapper.insertは引数の情報をすべてinsertするメソッド。(実装内容は割愛))

MusicMutationResolver
@Component
public class MusicMutationResolver implements GraphQLMutationResolver {

	@Autowired
	MusicMapper mapper;

	public Music registerMusic(String title) {
		Music music = new Music();
		music.setMusicId(generateId());
		music.setTitle(title);
		mapper.insert(music);
		return music;
	}
}

クエリ発行

mutationの場合はPOSTでAPIを呼び出す。クエリはJSON形式のパラメータとして添付する。
※クエリ内の"をエスケープすること

$ curl -s "http://localhost:8080/graphql" -X POST -H "Content-Type: application/json" -d '{"query":"mutation{registerMusic(title:\"NO WAR in the future\"){musicId,title}}"}'
{"data":{"registerMusic":{"musicId":"hogehoge","title":"NO WAR in the future"}}}

GraphiQL

GraphiQLで試す場合は左側にmutation{...}を記述し、その内側にqueryと同様にAPI名、引数、出力内容を記述する。

GraphiQL_mutation.PNG

参考

23
23
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
23
23

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?