GraphQL 是一种用于 API 的查询语言。 GraphQL 对你的 API 中的数据提供了一套易于理解的完整描述,使得客户端能够准确地获得它需要的数据,而且没有任何冗余,也让 API 更容易地随着时间推移而演进,还能用于构建强大的开发者工具。GraphQL 查询不仅能够获得资源的属性,还能沿着资源间引用进一步查询。典型的 REST API 请求多个资源时得载入多个 URL,而 GraphQL 可以通过一次请求就获取你应用所需的所有数据。GraphQL API 基于类型和字段的方式进行组织,而非入口端点。你可以通过一个单一入口端点得到你所有的数据能力。
Facebook 的移动应用从 2012 年就开始使用 GraphQL。GraphQL 规范于 2015 年开源,现已经在多种环境下可用,并被各种体量的团队所使用。
最好的文档当然是 官方文档
这里为了尽量的展示整个集成流程,所以会使用SpringBoot来进行整体框架搭建。但是会将更多的流程聚焦在GraphQL中。所以第一步是引入GraphQL的jar包,关于SpringBoot的搭建,可以借助 start.spring.io/ 来快速搭建。这里不做过多叙述。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</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>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<!--集成GraphQL-->
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java</artifactId>
<version>9.0</version>
</dependency>
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java-tools</artifactId>
<version>5.2.3</version>
</dependency>
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java-servlet</artifactId>
<version>5.2.0</version>
</dependency>
</dependencies>
复制代码
这里可以考虑实现一个具体的场景,就以二手书网站卖书举例好了。在GraphQL中查询和修改(增加,删除,更新)都是分别对应Query和Mutation.GraphQL支持的数据类型只有Int(Java中的Integer),Float,String,Enum以及Type(相当于Java中的Class),所以对于其他不支持的类型需要使用scalars自定义。
type Book {
id: Long
name: String
url: String
person: Person
}
# 注意这里的关键字是 input
input PersonParam {
name: String
age: Int
}
type Person {
name: String
age: Int
}
type Query {
books(name: String, size: Int): [Book]
}
type Mutation {
newBook(name: String, url: String, person: PersonParam): Long
}
schema {
query: Query
mutation: Mutation
}
scalar Long
复制代码
在GraphQL中如果参数是一个对象而不是标量值(Float,Int,String,Enum)的话,那么数据类型需要申明为input.上面可以看到PersonParam和Person的字段都是一样的,那么两个可以使用为同一个吗? 我告诉你不行,因为PersonParam是输入参数,而Person是输出参数,两者在GraphQL中不能混为一谈,如果你这样做了,程序会报错。
@Component
public class Query implements GraphQLQueryResolver {
@Autowired
private BookRespository bookRespository;
//方法名称,参数以及返回类型要和schema.graphqls文件中的定义保持一致.DataFetchingEnvironment是GraphQL提供的上下文,其中可以获取到request,response
public List<Book> books(String name,Integer size,DataFetchingEnvironment env) {
//AuthContext context = env.getContext() 用于权限过滤
return bookRespository.getBooks(name,size);
}
}
@Component
public class Mutation implements GraphQLMutationResolver {
@Autowired
private BookRespository bookRespository;
//接口需要和schema.graphqls中保持一致
public Long newBook(String name, String url, PersonParam personParam) {
Person person = new Person(personParam.getName(),personParam.getAge());
return bookRespository.saveBook(name,url,person);
}
}
复制代码
这里的接口实际上也是请求的最终落地点,可以想成SpringMVC中的Controller,最终的执行逻辑就会从这里开始。
spring.data.mongodb.uri=mongodb://localhost:27017/gln-local-test 复制代码
@Component
public class BookRespository {
@Autowired
private MongoTemplate mongoTemplate;
public List<Book> getBooks(String name, Integer size) {
if(null == size) {
size = 10;
}
Query query = new Query();
query.addCriteria(Criteria.where("name").alike(Example.of(name))).limit(size);
return mongoTemplate.findAll(Book.class, "book");
}
public Long saveBook(String name, String url, Person person) {
long id = new Random().nextLong();
Book book = Book.builder().id(id).name(name).url(url).person(person).build();
mongoTemplate.save(book,"book");
return id;
}
}
复制代码
刚才提到了GraphQL,它只提供了Int,Float,String,Enum以及自定义Type(class)类型,对于其他的需要自定义,实际上也就是定义如何序列化以及反序列化,这种自定义类型被称为scalar.刚才接口中定义了Long,所以我们需要写一个Long的scalar.实际上就是自定义序列化以及反序列化的规则
public class Scalars {
public final static GraphQLScalarType JAVA_LONG = new GraphQLScalarType("Long", "Java Long scalar",
new Coercing() {
@Override
public Long serialize(Object dataFetcherResult) throws CoercingSerializeException {
return Long.parseLong(dataFetcherResult.toString());
}
@Override
public Object parseValue(Object input) throws CoercingParseValueException {
return serialize(input);
}
@Override
public Long parseLiteral(Object input) throws CoercingParseLiteralException {
if(input instanceof StringValue) {
return Long.parseLong(((StringValue) input).getValue());
} else if(input instanceof IntValue) {
return ((IntValue) input).getValue().longValue();
}
return null;
}
});
}
复制代码
之前的配置只是对接口定义的诠释,但是要谈到和SpringMVC对接其实还没有,那么接下来的配置就是和SpringMVC进行对接了
@Component
public class GraphqlEndpoint extends SimpleGraphQLServlet {
@Autowired
public GraphqlEndpoint(Query query, Mutation mutation) {
super(SchemaParser.newParser()
.file("schema.graphqls")
//引入resovler
.resolvers(query,mutation)
//引入刚才定义的scalar
.scalars(Scalars.JAVA_LONG)
.build()
.makeExecutableSchema());
}
}
//统一的入口,graphQL通过这个统一入口将请求进行分发给各个resolver
@Controller
public class GraphqlController {
@Autowired
private GraphQLServlet graphQLServlet;
@RequestMapping("/graphql")
public void graphql(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
graphQLServlet.service(request,response);
}
}
复制代码
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
private String name;
private String age;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Book {
private Long id;
private String name;
private String url;
private Person person;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PersonParam {
private String name;
private String age;
}
复制代码
接下来就要开始发起请求了,那么我们可以使用GraphQL Playground(或者google graphiql插件)或者post man来进行测试,这里我两个都使用
query后面那一串其实是 query { books(name:"MongoDB",size:10) { name url person { name age } } } 的url转义。所以如果使用get方法发起请求,将查询参数放到url后面,需要将参数转义之后发起请求。如果使用post方法则只需要将请求体放到body中即可
这里给出一个自定义的实现,在GraphqlEndpoint复写下面的方法,然后在Query或者Mutation中就可以通过DataFetchingEnvironment获取到自定义的context,通过其中的request来进行权限验证了。
protected GraphQLContext createContext(Optional<HttpServletRequest> request, Optional<HttpServletResponse> response) {
//从request获取到cookie token等能证明用于身份的数据,解析出user
return AuthContext(user,request,response);
}
public class AuthContext extends GraphQLContext {
private final AuthUser authUser;
public AuthContext(AuthUser user, Optional<HttpServletRequest> request, Optional<HttpServletResponse> response) {
super(request, response);
this.authUser = user;
}
public AuthUser getAuthUser() {
return authUser;
}
}
复制代码
http://graphql.github.io/learn/schema/