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

2024-01-01 14:47:09 浏览数 (2)

要实现的 API 的数据模型


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

各表的用途如下。

由于这只是个简单的例子,所以并没有完全规范化,请不要太在意。

  • Account 表:管理用户的账户信息。
  • Service_Group 表:管理用户所属的服务组,而服务组可以拥有多个团队。
  • Team 表:管理用户所属的团队。

创建空白项目


首先,使用 Spring Initializr 创建一个空白项目并创建所需的包和目录。

所使用的构建工具、JDK、Spring Boot 及依赖库如下:

框架/库等

版本

OpenJDK

11

SpringBoot

2.7.1

Maven

3.5.4

Spring Web

-

Spring for GraphQL

-

Lombok

-

H2 Database

-

log4j2

-

此外,由于 Log4j2 不能在 Spring Initializr 中指定,因此需要直接将依赖关系添加到 pom.xml 中。

在这之前,需要通过 <exclusion> 来排除由 spring-boot-starter 依赖的日志库。

代码语言: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>

示例应用程序结构


项目结构如下:

代码语言:bat复制
spring-graphql-sample
 ├ src
 │  ├ main
 │  │  ├ java/com.spring.graphql.example 
 │  │  │  ├ controller
 │  │  │  ├ entity
 │  │  │  ├ repository
 │  │  │  └ Main.java 
 │  │  │
 │  │  └ resources
 │  │     ├ graphql
 │  │     │  └ graphql-schema.graphqls --- GraphQL 文件
 │  │     ├ aplication.yaml
 │  │     ├ log4j2.xml --- 日志输出配置
 │  │     ├ schema.sql --- H2 数据库表初始化脚本
 │  │     └ data.sql  --- H2 数据库数据初始化脚本
 │  │
 │  └ test --- 用于测试(本例未使用)
 └ pom.xml

实现准备


在直接实现 GraphQL API 之前,将对项目进行配置,包括将数据流入数据库和设置日志。

准备工作1:表定义和数据库连接定义设置

因为单独设置每个 DBMS 太麻烦了,所以将使用 H2DB。

为了在应用启动时将表和数据自动导入到 H2DB,将以下内容保存在 schema.sql 中。

(由于省略了 data.sql 的内容,请根据需要自行设置适当的数据)

代码语言: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 文件中进行数据库连接的定义设置。

代码语言: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复制
<?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>

GraphQL API 实现


前期准备工作已完成,现在将开始实现主题的 GraphQL API。

步骤1:定义 GraphQL 模式


在 resources/graphql/ 中创建一个名为 graphql-schema.graphqls 的文件,并定义 GraphQL 模式和查询。

在这个例子中,将定义一个名为 accountById 的查询。

该查询允许通过将账户ID作为参数发送到API请求,获取与账户ID关联的账户信息,以及该账户所属的服务组信息和团队信息。

虽然没有太多实际意义,但还可以从获取的团队信息中获取团队所属的服务组信息。

GraphQL 模式的定义与数据库表定义不同,推荐以数据使用者易于理解的形式定义模式。

关于模式定义的规则和思考方式,请参考这个链接。

代码语言: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 类


创建用于存储从数据库中检索的数据的 DTO 类,并将其放置在 entity 包中。

由于定义了三个表,因此将创建每个表的DTO类。

此外,为了避免繁琐地编写 Setter/Getter,使用了 Lombok。

对于主键和关联变量,使用 @Id 注解。

代码语言:java复制
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;
}
代码语言:java复制
ServiceGroup.java
@Setter
@Getter
public class ServiceGroup {
    @Id
    private String serviceGroupId;
    private String serviceGroupName;
}
代码语言:java复制
@Setter
@Getter
public class Team {
    @Id
    private String teamId;
    private String belongingServiceGroupId;
    private String teamName;
    private String teamAuthority;
}

步骤3:实现 Repository 接口


将使用 Spring Data JDBC 与数据库进行交互。

实现与每个表对应的 Repository 接口,并将其存储在 repository 包中。

(由于这次是简单的表结构,按表分别实现接口。)

通过 extends 指定的 CrudRepository 已经默认提供了 findById 方法,因此在以主键作为参数检索数据时,不需要单独实现数据库查询。

代码语言:java复制
public interface AccountRepository extends CrudRepository<Account, String>{
}
代码语言:java复制
public interface ServiceGroupRepository extends CrudRepository<ServiceGroup, String> {
}

对于 TeamRepository,由于需要实现除主键之外的参数的 SELECT 查询,因此需要按照以下示例定义方法。

代码语言: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 类

代码语言:java复制
@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 端点路径的配置和启用。

代码语言:yml复制
spring:
  # 省略了数据源定义
  graphql:
    graphiql:
      enabled: true
      path: /graphiqls

至此,API 的实现已经完成。

接下来,将启动 API 应用程序并进行操作确认。

启动 GraphQL API 服务器并进行操作确认


要启动 API 应用程序,只需运行 Main.java。

可以使用 Maven 进行构建并运行 JAR 文件的方法,也可以使用 IDE 功能进行运行,具体方法随意选择。

此外,将使用 GraphiQL 作为 GraphQL 的客户端工具。

  • 端点:http://localhost:8080/graphql
  • 方法:POST

操作确认1


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

代码语言:GraphQL复制
query {
    accountById(accountId:"ACC01") {
        accountId,
        userName,
        age,
        accountType
}

操作确认2


接下来,将获取与帐户信息相关的各种信息。

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

代码语言:GraphQL复制
query {
    accountById(accountId:"ACC01") {
        accountId,
        userName,
        age,
        accountType,
        serviceGroup {
            serviceGroupId
            serviceGroupName
        }
        team {
            teamId
            teamName
            teamAuthority
            serviceGroup {
                serviceGroupId
                serviceGroupName
            }
        }
    }
}

最后


这次介绍了简单查询示例的实现步骤,下一步可以尝试一些更高级的内容,如Mutation和分页。

0 人点赞