13
11

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 1 year has passed since last update.

Spring for GraphQLを利用したGraphQL APIの構築手順

Last updated at Posted at 2022-07-23

Spring for GraphQLとは

JavaでGraphQL APIを構築する場合はGraphQL Javaを利用することが一般的でした。
SpringBootを利用する場合も同様でしたが、2022年5月にSpring for Javaの1.0.0がリリースされSpringとして公式にGraphQLをサポートを提供する事になりました。
まだリリースから日が経っていないこもあり、Spring for GraphQLの公式ドキュメントサンプルは2022年7月現在であまり充実した内容ではないないと思います。

今回はサンプルやドキュメントを参考に独自にSpring for GraphQLを利用したAPIの実装を行っていきます。

今回実装するAPIのデータモデル

DBでストアされた下記テーブルについてGraphQLのスキーマに再定義し柔軟性の高いデータを取得できるAPIを実装します。
まずは、下図のようなデータモデルをベースとしてGraphQLのスキーマ定義を行っていく。

er.png

各テーブルの利用用途は以下の通りです。
また、適当に作ったので完全に正規化できていないですがそこは気にしないでください。

  • Accountテーブル:ユーザのアカウント情報を管理します。
  • Service_Groupテーブル:ユーザが所属するサービスグループを管理し、サービスグループは複数のチームを持ちます。
  • Teamテーブル:ユーザが所属するチームを管理します。

ブランクプロジェクトの作成

まずはSpring Initializrでブランクプロジェクトを作成し必要なパッケージとディレクトリを作成します。
利用するビルドツール、JDK、SpringBootおよび依存ライブラリは以下の通りです。

FW/ライブラリ等 バージョン
AdoptOpenJDK 11
SpringBoot 2.7.1
Maven 3.5.4
Spring Web -
Spring for GraphQL -
Lombok -
H2 Database -
log4j2 -

またLog4j2はSpring Initializrで指定できないのでpom.xmlに直接依存関係を追加します。
その際にspring-boot-starter<が依存するロギングライブラリを除外してやる必要があるので<exclusion>で除外します。

pom.xml
<dependency>
  <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <exclusions>
      <exclusion>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-logging</artifactId>
      </exclusion>
    </exclusions>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>

サンプルアプリ構成

プロジェクト構成は下記構成とします。

spring-graphql-sample
 ├ src
 │  ├ main
 │  │  ├ java/com.spring.graphql.example 
 │  │  │  ├ controller
 │  │  │  ├ entity
 │  │  │  ├ repository
 │  │  │  └ Main.java 
 │  │  │
 │  │  └ respurces
 │  │     ├ graphql
 │  │     │  └ graphql-schema.graphqls --- graphqlsファイル
 │  │     ├ aplication.yaml
 │  │     ├ log4j2.xml --- ログ出力設定
 │  │     ├ schema.sql --- H2 DBテーブル初期化スクリプト
 │  │     └ data.sql  --- H2 DBデータ初期化スクリプト
 │  │
 │  └ test --- テスト用(今回使用せず)
 └ pom.xml

実装準備

いきなりGraphQLのAPIを実装するの前にプロジェクトにDBに流すデータやログの設定を行います。

準備作業1:テーブル定義とDB接続定義設定

DBMSを個別に設定するのが面倒なのでH2DBを利用します。
アプリ起動時にH2DBにテーブルとデータを自動で流し込むためにschema.sqlに下記内容で保存します。
data.sqlの内容は省略するので各自で適当なデータを設定してください)

schema.sql
-- Service Group
CREATE TABLE service_group (
  service_group_id VARCHAR(10) PRIMARY KEY COMMENT 'サービスグループID',
  service_group_name VARCHAR(40) NOT NULL COMMENT 'サービスグループ名'
);
-- Team
CREATE TABLE team (
  team_id VARCHAR(10) PRIMARY KEY COMMENT 'チームID',
  belonging_service_group_id VARCHAR(10) NOT NULL COMMENT '所属サービスグループグID',
  team_name VARCHAR(40) NOT NULL COMMENT 'チーム名',
  team_authority VARCHAR(15) COMMENT 'チーム権限',
  FOREIGN KEY (belonging_service_group_id) REFERENCES service_group (service_group_id)
);
-- Account Table
CREATE TABLE account (
  account_id VARCHAR(10) PRIMARY KEY COMMENT 'アカウントID',
  user_name VARCHAR(40) NOT NULL COMMENT 'ユーザ名',
  age Int NOT NULL COMMENT '年齢',
  account_type VARCHAR(10) NOT NULL COMMENT 'アカウント分類',
  belonging_service_group_id VARCHAR(10) NOT NULL COMMENT '所属サービスグループID',
  belonging_team_id VARCHAR(10) NOT NULL COMMENT '所属チームID',
  FOREIGN KEY (belonging_service_group_id) REFERENCES service_group (service_group_id),
  FOREIGN KEY (belonging_team_id) REFERENCES team (team_id)
);

次にapplication.yamlファイルにDB接続定義を設定しておきます。

application.yaml
spring:
  datasource:
    platform: h2
    driver-class-name: org.h2.Driver
    url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=1;DB_CLOSE_ON_EXIT=FALSE;MODE=DB2
    username: sa
    password: ''

準備作業2:ログ設定

ロギングにはlog4j2を利用します。

log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="error">

    <Properties>
        <Property name="format1">[%d{yyyy/MM/dd HH:mm:ss.SSS}] [%t] [%-6p] [%c{10}] : %m%n</Property>
    </Properties>

    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout>
                <pattern>${format1}</pattern>
            </PatternLayout>
        </Console>
    </Appenders>

    <Loggers>
        <Root level="info">
            <AppenderRef ref="Console" />
        </Root>
    </Loggers>
</Configuration>

GraphQLAPIの実装

事前準備が完了したので本題のGraphQLAPIを実装していきます。

手順1:GraphQLのスキーマ定義

resources/graphql/graphql-schema.graphqlsという名前のファイルを作成しGraphQLのスキーマおよびクエリを定義します。
今回はaccountByIdというクエリを定義します。

このクエリはアカウントIDをパラメータとしてAPIリクエストを投げることで、アカウントIDに紐づくアカウント情報、そのアカウントが所属するサービスグループ情報、チーム情報を取得することが可能となっています。
またあまり意味はありませんが、取得したチーム情報からチームが所属するサービスグループ情報も取得可能となっています。

GraphQLのスキーマ定義はDBのテーブル定義とは異なりデータを利用する側が利用しやすい形でスキーマを定義することが推奨されています。
スキーマ定義に関するルールや考え方はこちらのリンクが参考になります。

graphql-schema.graphqls
# クエリ定義
type Query {
    accountById(accountId: ID): Account
}
# タイプ定義
type ServiceGroup {
    serviceGroupId: ID!
    serviceGroupName: String
    teams: [Team]
}
type Team {
    teamId: ID!
    teamName: String
    teamAuthority: String
    serviceGroup: ServiceGroup!
}
type Account {
    accountId: ID!
    userName: String
    age: Int
    accountType: String
    serviceGroup: ServiceGroup!
    team: Team!
}

# Enum定義
enum TeamAuthority {
    PRIVILEGE
    DEVELOP
    AUDIT
}
enum AccountType {
    ADMIN
    DEVELOPER
    GUEST
}

手順2:DTOクラス実装

DBから取得したデータを格納するDTOクラスをentityパッケージに作成します。
今回は3つのテーブルを定義しているのでそれぞれのテーブル用のDTOクラスを作成します。
また、Setter/Getterをいちいち書くのが面倒なのでLombokを利用しています。
PKと紐づく変数には@Idアノテーションを付与しています。

Account.java
@Setter
@Getter
@AllArgsConstructor
public class Account {
    @Id
    private String accountId;
    private String userName;
    private int age;
    private String accountType;
    private String belongingServiceGroupId;
    private String belongingTeamId;
}
ServiceGroup.java
@Setter
@Getter
public class ServiceGroup {
    @Id
    private String serviceGroupId;
    private String serviceGroupName;
}
Team.java
@Setter
@Getter
public class Team {
    @Id
    private String teamId;
    private String belongingServiceGroupId;
    private String teamName;
    private String teamAuthority;
}

手順3:RepositoryIFの実装

DBとのやり取りにはSpring Data JDBCを利用します。
各テーブルと対応するRepositoryIFを実装し、repositoryパッケージに格納します。
(今回は単純なテーブル構造なのでテーブル単位でIFを分けています。)

extendsで指定しているCrudRepositoryfindByIdメソッドがデフォルトで用意されているためPKをパラメータとしてデータを取得する場合は個別にDBクエリを実装する必要はありません。

AccountRepository.java
public interface AccountRepository extends CrudRepository<Account, String>{
}
ServiceGroupRepository.java
public interface ServiceGroupRepository extends CrudRepository<ServiceGroup, String> {
}

TeamRepositoryについてはPK以外をパラメータとしたSELECTクエリを実装する必要があるため下記例の通りにメソッドを定義します。

TeamRepository.java
public interface TeamRepository extends CrudRepository<Team, String> {

    @Query("SELECT team_id, belonging_service_group_id, team_name, team_authority" +
            " FROM team WHERE belonging_service_group_id = :serviceGroupId")
    List<Team> findByServiceGroupId(String serviceGroupId);
}

手順4:Controllerクラスの実装

APIリクエストを受け取るControllerクラスを実装します。

@QueryMappingアノテーションを付けたメソッドにて受け付けたGraphQLクエリを処理するメソッドを定義します。
今回はaccountByIdが対象のGraphQLクエリとなり、accountId@Argumentを付けることで引数を受け取ることを明記します。

また、@SchemaMappingにはAccountGraphQLスキーマに紐づくserviceGroupTeamを取得するための処理を実装します。
今回は、Accountに紐づくServiceGroupTeamおよびTeamに紐づくServiceGroupを取得するために3つのメソッドを定義しています。

@Controller
public class AccountGraphqlController {

    private final Logger logger = LogManager.getLogger(AccountGraphqlController.class);

    private AccountRepository accountRepository;

    private ServiceGroupRepository serviceGroupRepository;

    private TeamRepository teamRepository;

    public AccountGraphqlController(final AccountRepository accountRepository,
                                    final ServiceGroupRepository serviceGroupRepository,
                                    final TeamRepository teamRepository) {
        this.accountRepository = accountRepository;
        this.serviceGroupRepository = serviceGroupRepository;
        this.teamRepository = teamRepository;
    }

    @QueryMapping
    public Account accountById(@Argument final String accountId) {
        logger.info("=== Query Call, queryByAccountId. === ");
        final Optional<Account> acc = accountRepository.findById(accountId);
        return acc.get();
    }

    @SchemaMapping
    public ServiceGroup serviceGroup(final Account account) {
        final Optional<ServiceGroup> sg = serviceGroupRepository.findById(account.getBelongingServiceGroupId());
        return sg.get();
    }

    @SchemaMapping
    public Team team(final Account account) {
        final Optional<Team> t = teamRepository.findById(account.getBelongingTeamId());
        return t.get();
    }

    @SchemaMapping
    public ServiceGroup serviceGroup(final Team team) {
        final Optional<ServiceGroup> sg = serviceGroupRepository.findById(team.getBelongingServiceGroupId());
        return sg.get();
    }
}

手順5:GraphQLエンドポイントURLの設定

application.yamlファイルにGraphQLのAPIエンドポイントパスの設定や有効化を行います。

application.yaml
spring:
  # データソース定義は省略
  graphql:
    graphiql:
      enabled: true
      path: /graphiqls

以上でAPIの実装は完了です。
次は実際にAPIアプリを起動して動作確認を行います。

GraphQLAPIサーバの起動と動作確認

APIアプリの起動にはMain.javaを実行するだけです。
Mavenでビルドしてjarを実行する方法や、IDEの機能で実行するなど実行方法は好きな方法で大丈夫です。

またGraphQLのクライアントツールとしてGraphiQLを利用することにします。

動作確認1

下記クエリを実行することで、アカウント情報が取得できました。

例1
query {
    accountById(accountId:"ACC01") {
        accountId,
        userName,
        age,
        accountType
}

image.png

動作確認2

次にアカウント情報とそれに紐づく各種情報を取得します。
下記クエリを実行することで、アカウント情報とそれに紐づくサービスグループ、チーム、チームに紐づくサービスグループ情報が一括で取得できました。

例2
query {
    accountById(accountId:"ACC01") {
        accountId,
        userName,
        age,
        accountType,
        serviceGroup {
            serviceGroupId
            serviceGroupName
        }
        team {
            teamId
            teamName
            teamAuthority
            serviceGroup {
                serviceGroupId
                serviceGroupName
            }
        }
    }
}

image.png

最後に

今回は簡単なクエリサンプルの実装手順を解説しましたが、次はMutationやページネーションなどの応用を試してみようかと思います。

13
11
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
13
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?