RPC选型入门测试系列文章
【1】GraphQL基础知识与Spring for GraphQL使用教程
【2】gRPC Java、Go、PHP使用例子
【3】Thrift RPC Java、Go、PHP使用例子
文章目录
GraphQL是一种用于API开发的查询语言和运行时环境。它由Facebook开发并于2015年开源。GraphQL的主要目标是提供一种更高效、灵活和易于使用的方式来获取和操作数据。与传统的RESTful API相比,GraphQL允许客户端精确地指定需要的数据,并减少了不必要的网络传输和数据处理。
采用GraphQL,甚至不需要有任何的接口文档,在定义了Schema之后,服务端实现Schema,客户端可以查看Schema,然后构建出自己需要的查询请求来获得自己需要的数据。
1、数据类型
1.1、标量类型
- Int -32位整型数字;
- Float-双精度浮点型;
- String-UTF‐8 字符序列;
- Boolean-布尔型,true 或者 false;
- ID-标识类型,唯一标识符,注意此ID为字符,如果使用Mysql自增长id,也会自动转为对应的字符串;
1.2. 高级数据类型
- Object - 对象,用于描述层级或者树形数据结构。Object类型有一个类型名,以及类型包含的字段。
type Product {
id: ID!
info: String!
price: Float
}
在此示例中,声明了Product对象类型,定义了3 个字段:
id:非空 ID 类型。
info:非空字符串类型。
price:浮点型。
- Interface-接口,用于描述多个类型的通用字;与 Object一样。
interface Product {
id: ID!
info: String!
price: Float
}
- Union-联合类型,用于描述某个字段能够支持的所有返回类型以及具体请求真正的返回类型;
- Enum-枚举,用于表示可枚举数据结构的类型;
enum Status {
Yes
No
}
type Product {
id: ID!
info: String!
price: Float
stat: Status
}
- Input-输入类型input本质上也是一个type类型,是作为Mutation接口的输入参数。换言之,想要定义一个修改接口(add,update,delete)的输入参数对象,就必须定义一个input输入类型。
input BookInput {
isbn: ID!
title: String!
pages: Int
authorIdCardNo: String
}
- List -列表,任何用方括号 ([]) 括起来的类型都会成为 List 类型。
type Product {
id: ID!
info: String
price: Float
images: [String]
}
- Non-Null-不能为 Null,类型后边加!表示非空类型。例如,String 是一个可为空的字符串,而String!是必需的字符串。
基本操作
- Query(只读操作)
#schema.graphqls定义操作
type Query {
allBooks: [Book]!
bookByIsbn(isbn: ID): Book
}
# 接口查询语法
query{
allBooks {
title
author {
name
age
}
}
}
- Mutation(可写操作)
#schema.graphqls定义操作
type Mutation {
createBook(bookInput: BookInput): Book
createAuthor(authorInput: AuthorInput): Author
}
# mutation{
# createAuthor(authorInput:{
# idCardNo: "341234567891234567",
# name:"test1",
# age:38
# }
# ){
# name
# age
# }
# }
- Subscription(订阅操作)
type Subscription {
greetings: String
}
2、Spring for GraphQL实例
GraphQL只是一种架构设计,具体的实现需要各个技术平台自己实现,目前主流的开发语言基本都已经有现成的类库可以使用,GraphQL Java就是Java平台的实现。
GraphQL Java虽然实现了GraphQL,但是只是一个执行GraphQL请求的引擎,用户在使用中需要创建自己的HTTP服务来提供服务。
Spring for GraphQL为基于GraphQL Java构建的Spring应用程序提供支持,来自 GraphQL Java 团队,它的目标是成为所有Spring、GraphQL应用程序的基础。
spring-graphql中定义的核心注解如下:
- @GraphQlController:申明该类是GraphQL应用中的控制器
- @QueryMapping:申明该类或方法使用GraphQL的query操作,等同于@SchemaMapping(typeName = “Query”),类似于@GetMapping
- @Argument:申明该参数是GraphQL应用的入参
- @MutationMapping:申明该类或方法使用GraphQL的mutation操作,等同于@SchemaMapping(typeName = “Mutation”)
- @SubscriptionMapping:申明该类或方法使用GraphQL的subscription操作,等同于@SchemaMapping(typeName = “Subscription”)
- @SchemaMapping:指定GraphQL操作类型的注解,类似@RequestMapping
2.1、项目目录
项目代码目录
2.2、数据库表
数据表结构,这里例子简单用了用户表和用户日志表
CREATE TABLE `admin_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4
CREATE TABLE `admin_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`user_id` varchar(255) DEFAULT NULL,
`visit_url` varchar(255) DEFAULT NULL,
`create_date` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4
2.3、GraphQL的schema.graphql
GraphQL对应的schema.graphql定义文件,注意GraphQL默认只支持标量类型,DateTime自定义类型使用graphql-java-extended-scalars:https://github.com/graphql-java/graphql-java-extended-scalars提供
scalar DateTime
type AdminUser{
id: ID!
name: String!
}
type AdminLog{
id: ID!
visitUrl: String
user: AdminUser!
createDate: DateTime
}
type Query {
allLogs:[AdminLog]
logByUser(userid: ID): [AdminLog]
}
type Mutation {
createUser(adminUserInput: AdminUserInput): AdminUser
createLog(adminLogInput: AdminLogInput): AdminLog
}
input AdminLogInput {
userId: String!
visitUrl: String
createDate: DateTime
}
input AdminUserInput {
name: String!
}
type Subscription {
greetings: String
}
2.4、Java代码
pom.xml依赖包文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.10</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.penngo.example</groupId>
<artifactId>graphql-app</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>graphql-app</name>
<description>graphql-app project for Spring Boot</description>
<properties>
<java.version>17</java.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-graphql</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.graphql-java/graphql-java-extended-scalars -->
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java-extended-scalars</artifactId>
<version>19.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>alimaven</id>
<name>Maven Aliyun Mirror</name>
<url>https://maven.aliyun.com/repository/central</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>aliyun-plugin</id>
<url>https://maven.aliyun.com/repository/public</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</project>
对应的数据库实体类
package com.penngo.example.entity;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.OffsetDateTime;
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class AdminLog {
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
private Long id;
private String userId;
private String visitUrl;
private OffsetDateTime createDate;
}
package com.penngo.example.entity;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class AdminUser {
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
private Long id;
private String name;
}
GraphQL对应的输入类
package com.penngo.example.entity;
import lombok.Data;
import java.time.OffsetDateTime;
@Data
public class AdminLogInput {
private String userId;
private String visitUrl;
private OffsetDateTime createDate;
}
package com.penngo.example.entity;
import lombok.Data;
@Data
public class AdminUserInput {
private String name;
}
对应的数据库操作类
package com.penngo.example.repository;
import com.penngo.example.entity.AdminUser;
import org.springframework.data.jpa.repository.JpaRepository;
public interface AdminUserRepository extends JpaRepository<AdminUser,Long> {
AdminUser findById(String userId);
}
package com.penngo.example.repository;
import com.penngo.example.entity.AdminLog;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface AdminLogRepository extends JpaRepository<AdminLog,Long> {
List<AdminLog> findAllByUserId(String userId);
}
对外服务接口类
package com.penngo.example.controller;
import com.penngo.example.entity.*;
import com.penngo.example.repository.AdminLogRepository;
import com.penngo.example.repository.AdminUserRepository;
import org.springframework.beans.BeanUtils;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.MutationMapping;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.graphql.data.method.annotation.SchemaMapping;
import org.springframework.stereotype.Controller;
import java.util.List;
@Controller
public class AdminLogController {
private final AdminUserRepository adminUserRepository;
private final AdminLogRepository adminLogRepository;
public AdminLogController(AdminUserRepository adminUserRepository, AdminLogRepository adminLogRepository){
this.adminLogRepository = adminLogRepository;
this.adminUserRepository = adminUserRepository;
}
@QueryMapping
public List<AdminLog> allLogs(){
List<AdminLog> logsList = adminLogRepository.findAll();
return logsList;
}
@QueryMapping
public List<AdminLog> logByUser(@Argument String userid){
return adminLogRepository.findAllByUserId(userid);
}
@SchemaMapping(typeName = "AdminLog" ,field = "user")
public AdminUser getAuthor(AdminLog adminLog){
AdminUser adminUser = adminUserRepository.findById(adminLog.getUserId());
return adminUser;
}
@MutationMapping
public AdminLog createLog(@Argument AdminLogInput adminLogInput){
AdminLog log = new AdminLog();
BeanUtils.copyProperties(adminLogInput,log);
return adminLogRepository.save(log);
}
}
package com.penngo.example.controller;
import com.penngo.example.entity.*;
import com.penngo.example.repository.AdminLogRepository;
import com.penngo.example.repository.AdminUserRepository;
import org.springframework.beans.BeanUtils;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.MutationMapping;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.stereotype.Controller;
import java.util.List;
@Controller
public class AdminUserController {
private final AdminUserRepository adminUserRepository;
private final AdminLogRepository adminLogRepository;
public AdminUserController(AdminUserRepository adminUserRepository, AdminLogRepository adminLogRepository){
this.adminLogRepository = adminLogRepository;
this.adminUserRepository = adminUserRepository;
}
@QueryMapping
public List<AdminLog> userById(@Argument String userid){
return adminLogRepository.findAllByUserId(userid);
}
@MutationMapping
public AdminUser createUser(@Argument AdminUserInput adminUserInput){
AdminUser user = new AdminUser();
BeanUtils.copyProperties(adminUserInput,user);
return adminUserRepository.save(user);
}
}
package com.penngo.example.controller;
import com.penngo.example.entity.AdminLog;
import com.penngo.example.repository.AdminLogRepository;
import com.penngo.example.repository.AdminUserRepository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.graphql.data.method.annotation.SubscriptionMapping;
import org.springframework.stereotype.Controller;
import reactor.core.publisher.Flux;
import java.time.Duration;
@Controller
public class GreetingController {
private final AdminUserRepository adminUserRepository;
private final AdminLogRepository adminLogRepository;
public GreetingController(AdminUserRepository adminUserRepository, AdminLogRepository adminLogRepository){
this.adminLogRepository = adminLogRepository;
this.adminUserRepository = adminUserRepository;
}
// 数据订阅,取最新的5条数据,每5秒发送一条给客户端,一共5次
@SubscriptionMapping
public Flux<AdminLog> greetings(){
System.out.println("greetings====================");
Page<AdminLog> logsList = adminLogRepository.findAll(PageRequest.of(0,5).withSort(Sort.Direction.DESC, "id"));
return Flux.fromStream(logsList.stream())
.delayElements(Duration.ofSeconds(5))
.take(5);
}
}
自定义日期数据类型DateTime
package com.penngo.example.component;
import graphql.scalars.ExtendedScalars;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.graphql.execution.RuntimeWiringConfigurer;
@Configuration
public class CustomScalarType {
@Bean
public RuntimeWiringConfigurer runtimeWiringConfigurer() {
return wiringBuilder -> wiringBuilder.scalar(ExtendedScalars.DateTime);
}
}
服务启动类
package com.penngo.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class GraphqlAppApplication {
public static void main(String[] args) {
SpringApplication.run(GraphqlAppApplication.class, args);
}
}
3、运行效果
3.1、添加用户
添加用户
mutation{
createUser(adminUserInput: {
name: "test1",
} )
{
id
name
} }
3.2、添加日志
添加日志
mutation{
createLog(adminLogInput: {
userId: "1",
visitUrl: "http://localhost:8080/method1"
createDate: "2023-09-17T19:39:57+08:00"
} )
{
id
visitUrl
createDate
} }
3.3、查询所有日志
查询所有日志
query{
allLogs{
id
visitUrl
user{
id
name
}
createDate
}
}
3.4、查询指定用户日志
查询指定用户日志
query{
logByUser(userid:"1") {
id
visitUrl
user{
id
name
}
createDate
}
}
3.5、数据订阅
数据订阅,订阅需要有websocket的支持。
subscription {
greetings
}
4、总结
使用Spring for GraphQL试用了GraphQL后,它实现按需取数据的功能。服务器开发人员和前端开发人员可以通过schema.graphqls定义文件,协定好接口和数据,省掉写接口文档的工作。
缺点可能就是需要一点学习成本,虽然提供数据嵌套可以通过一个请求获取所有数据,但是嵌套复杂可能引起性能问题。
Spring for GraphQL官方参考:https://docs.spring.io/spring-graphql/docs/current/reference/html/#overview