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/