使用Spring for GraphQL构建GraphQL API的步骤

Spring for GraphQL是一个用于构建GraphQL API的Spring框架。

一般来说,构建Java的GraphQL API通常使用GraphQL Java。
如果使用SpringBoot,也是类似的情况,但是在2022年5月,Spring for Java发布了1.0.0版,并官方提供了对GraphQL的支持。
因为发布时间还不久,所以截至2022年7月,Spring for GraphQL的官方文档和示例内容还不是很丰富。

这次我们将参考样例和文档,独自地使用Spring for GraphQL来实现API的实施。

 

这次要实施的API数据模型

将存储在数据库中的以下表重新定义为GraphQL模式,并实现能够获取灵活数据的API。
首先,我们将根据下图的数据模型进行GraphQL模式定义。

er.png

以下是各个表的使用目的。
另外,由于是随意创建的,可能没有完全进行规范化,请不要太在意这一点。

    • Accountテーブル:ユーザのアカウント情報を管理します。

 

    • Service_Groupテーブル:ユーザが所属するサービスグループを管理し、サービスグループは複数のチームを持ちます。

 

    Teamテーブル:ユーザが所属するチームを管理します。

创建空白项目

首先,在Spring Initializr上创建一个空白项目,并创建所需的包和目录。
使用的构建工具、JDK、SpringBoot和依赖库如下所示。

FW/ライブラリ等バージョンAdoptOpenJDK11SpringBoot2.7.1Maven3.5.4Spring Web-Spring for GraphQL-Lombok-H2 Database-log4j2-

在Spring Initializr中无法指定Log4j2,因此需要直接在pom.xml中添加依赖项。为了排除spring-boot-starter依赖的日志库,需要使用进行排除。

<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之前,我们需要在项目中配置将数据和日志发送到数据库。

准备任务1: 表定义和数据库连接定义设置

因为分别设置DBMS会很麻烦,所以我们使用H2DB。
为了在应用启动时自动将表和数据流入H2DB,我们将以下内容保存到schema.sql中。
(省略data.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文件中配置数据库连接定义。

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进行日志记录。

<?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的实现

由于事前准备工作已经完成,我们将开始实现主题的GraphQL API。

第一步:GraphQL的Schema定义

在resources/graphql/目录下创建一个名为graphql-schema.graphqls的文件,并定义GraphQL的模式和查询。
这次我们定义一个名为accountById的查询。

通过将账户ID作为参数发送API请求,可以获取与该账户ID相关联的账户信息、所属的服务组信息和团队信息。
此外,虽然意义不大,但也可以从获取的团队信息中获取所属的服务组信息。

在GraphQL中,推荐使用一种更易于数据使用者使用的形式来定义模式,与数据库的表定义不同。关于模式定义的规则和思考方式,请参考此链接。

# クエリ定義
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类

将从数据库中获取的数据存储到entity包中创建的DTO类中。
由于本次定义了三个表,因此需要为每个表创建相应的DTO类。
此外,为了避免繁琐地编写Setter/Getter方法,使用了Lombok。
对于与主键相关的变量,添加了@Id注解。

@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;
}
@Setter
@Getter
public class ServiceGroup {
    @Id
    private String serviceGroupId;
    private String serviceGroupName;
}
@Setter
@Getter
public class Team {
    @Id
    private String teamId;
    private String belongingServiceGroupId;
    private String teamName;
    private String teamAuthority;
}

步骤3:实现RepositoryIF

在与数据库的交互中,我们使用了Spring Data JDBC。我们会实现与每个表对应的RepositoryIF,并将其存放在repository包中。(由于这次是简单的表结构,所以我们将IF按照表的单位进行了分离。)

由于CrudRepository中已经默认提供了findById方法,所以如果要通过主键参数获取数据,无需单独实现数据库查询。

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

需要实现一个关于 TeamRepository 的方法,该方法的参数除了 PK 之外还有其他的 SELECT 查询,方法定义如下所示。

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:实现控制器类

实现一个能够接收API请求的控制器类。

在带有@QueryMapping注释的方法中定义处理接收到的GraphQL查询的方法。
本次操作中,accountById是目标GraphQL查询,并通过在accountId上使用@Argument注释来明确接收参数的过程。

此外,我们将实现@SchemaMapping,以获取与AccountGraphQL模式相关联的serviceGroup和Team的处理。为了获取与Account相关联的ServiceGroup、Team以及与Team相关联的ServiceGroup,我们定义了三个方法。

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

步骤五:设置GraphQL端点URL。

在application.yaml文件中进行GraphQL API端点路径的配置和启用。

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

以上是API的实现完成。
接下来我们将启动实际的API应用程序并进行操作确认。

启动和测试GraphQLAPI服务器。

只需执行Main.java即可启动API应用程序。可以使用Maven构建并执行jar文件,也可以使用IDE的功能进行执行,无论使用哪种方法都可以。

我决定将GraphiQL用作为GraphQL的客户端工具。

    • エンドポイント:http://localhost:8080/graphql

メソッド:POST

确认动作1

通过执行以下查询,我们成功获取了账户信息。

query {
    accountById(accountId:"ACC01") {
        accountId,
        userName,
        age,
        accountType
}
image.png

确认动作2

通过执行以下查询,可以一次性获取帐户信息及其关联的服务组、团队和与团队关联的服务组信息。

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

最终

我們在這次講解中介紹了簡單的查詢示例實施步驟,但下次我們想試試Mutation和分頁等應用。

bannerAds