一 GraphQL简介
GraphQL是什么
为API而生的专用查询语言
GraphQL是一门为API和运行时而生的查询语言。它可以使用您已有的数据对这些查询进行填充。 GraphQL在您的API中,提供了一个完整的,易于理解的数据描述, 可以给予您的客户端一个权利,可以精确地描述他们所需要的数据,并不拖泥带水。 随着时间的推移,使得API的进化更加容易,并且开启强大的开发者工具
使用场景
当提起API设计的时候,大家通常会想到SOAP,RESTful等设计方式,从2000年RESTful的理论被提出的时候,在业界引起了很大反响,因为这种设计理念更易于用户的使用,所以便很快的被大家所接受。我们知道REST是一种从服务器公开数据的流行方式。当REST的概念被提及出来时,客户端应用程序对数据的需求相对简单,而开发的速度并没有达到今天的水平。因此REST对于许多应用程序来说是非常适合的。然而在业务越发复杂,客户对系统的扩展性有了更高的要求时,API环境发生了巨大的变化。特别是从下面三个方面在挑战api设计的方式:
Facebook开发GraphQL的最初原因是移动用户的增加、低功耗设备和松散的网络。GraphQL最小化了需要网络传输的数据量,从而极大地改善了在这些条件下运行的应用程序。
前端框架和平台运行客户端应用程序的异构环境使得我们在构建和维护一个符合所有需求的API变得困难,使用GraphQL每个客户机都可以精确地访问它需要的数据。
在不同前端框架,不同平台下想要加快产品快速开发变的越来越难
持续部署已经成为许多公司的标准,快速的迭代和频繁的产品更新是必不可少的。对于REST api,服务器公开数据的方式常常需要修改,以满足客户端的特定需求和设计更改。这阻碍了快速开发实践和产品迭代。
接口架构设计
我们以用户 和账户 两个服务作为举例,进行,通过不同的接口设计实现功能.
二. 原理分析
客户端 : 不管是手机端还是PC端,请求发送到服务端钱经过 GraphQL Client 转换成客户端Schema, 这里面描述了客户端的对数据的诉求,调用哪个方法, 传递什么样的参数,期望返回值有哪些字段.
服务端 : 服务端拿到这段 Schema 之后,通过事先定义好的服务端 Schema 接收请求参数并执行对应的 resolve 函数提供数据服务。
识别和相应客户端的过程可以看做以下三个过程: 解析 校验 执行
解析阶段:为了识别客户端 Schema, graphql-js 定义了一系列的特征标识符:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 # 为了识别客户端 Schema, `graphql-js` 定义了一系列的特征标识符: export const TokenKind = Object.freeze({ BANG: '!', DOLLAR: '$', PAREN_L: '(', PAREN_R: ')', SPREAD: '...', COLON: ':', EQUALS: '=', BRACKET_L: '[', BRACKET_R: ']', ... }); # 定义了 AST 语法树规范 /** * The set of allowed kind values for AST nodes. */ export const Kind = Object.freeze({ // Name NAME: 'Name', // Document DOCUMENT: 'Document', OPERATION_DEFINITION: 'OperationDefinition', VARIABLE_DEFINITION: 'VariableDefinition', VARIABLE: 'Variable', // Values INT: 'IntValue', FLOAT: 'FloatValue', STRING: 'StringValue', BOOLEAN: 'BooleanValue', ... });
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 { "errors" : [ { "message" : "Cannot query field " fetchByGen" on type " Query". Did you mean " fetchByGender"?" , "locations" : [ { "line" : 3 , "column" : 9 } ] } ] }
1 2 3 4 5 6 7 8 9 10 11 12 13 { "data" : { "User" : { "name" : "张三" , "age" : 12 , "identification" : "12332112321321321" , "Account" : [ { "id" : "fc9a45ce-dd91-4ed2-8e43-a26412a03edd" , "balance" : 12.32 } , { "id" : "8d4832be-22f5-40c8-ab37-fc488843d4f1" , "balance" : 19999.1 } ] } } }
三. 使用Springboot来搭建GraphQL后端程序 1. 引入开源的GraphQL的相关jar包 1 2 3 4 5 6 7 8 9 10 <dependency > <groupId > com.graphql-java</groupId > <artifactId > graphql-spring-boot-starter</artifactId > <version > 5.0.2</version > </dependency > <dependency > <groupId > com.graphql-java</groupId > <artifactId > graphql-java-tools</artifactId > <version > 5.2.4</version > </dependency >
注意事项 : 根据SpringBoot的版本不同引入的GraphQL的版本也会不相同
2. 建数据库表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 CREATE TABLE `User ` ( `id` varchar (50 ) NOT NULL AUTO_INCREMENT COMMENT '主键' , `created_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间' , `updated_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间' , `name` varchar (50 ) DEFAULT NULL COMMENT 'name' , `identification` varchar (50 ) DEFAULT NULL COMMENT 'identification' , `age` varchar (50 ) DEFAULT NULL COMMENT 'age' , PRIMARY KEY (`id`) ) ENGINE= InnoDB DEFAULT CHARSET= utf8; CREATE TABLE `Account` ( `id` varchar (50 ) NOT NULL AUTO_INCREMENT COMMENT '主键' , `created_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间' , `updated_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间' , `balance` varchar (50 ) DEFAULT NULL COMMENT '余额' , `status` varchar (50 ) DEFAULT NULL COMMENT '状态' , `user_id` bigint (20 ) NOT NULL , PRIMARY KEY (`id`) ) ENGINE= InnoDB DEFAULT CHARSET= utf8;
3. 重点:建立解析器Resolver UserResolver.java 用户解析字段
1 2 3 4 5 6 7 8 9 10 @Component @AllArgsConstructor public class UserResolver implements GraphQLResolver <Author> { private AccountRepository accountRepository; public List<Account> getBooks (User user) { return accountRepository.findByUserId(user.getId()); }
AccountResolver.java 账户信息解析字段
1 2 3 4 5 6 7 8 9 10 @Component @AllArgsConstructor public class AccountResolver implements GraphQLResolver <Book> { private UserRepository userRepository; public User getUser (Account account) { return authorRepo.findUserById(account.getUserId()); } }
Mutation.java 用户数据的新增和更新的基本操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 @Component @AllArgsConstructor public class Mutation implements GraphQLMutationResolver { private UserRepository userRepository; private AccountRepository accountRepository; public User newUser (String name, int age, String identification) { User user = new User (); author.setName(name); author.setAge(age); author.setIdentification(identification); return userRepository.save(user); } public Account newAccount (String balance, String status, String userId) { Account account = new Account (); book.setBalance(balance); book.setStatus(status); book.setUserId(userId); return accountRepository.save(account); } public Account saveAccount (AccountInput input) { Account account = new Account (); book.setBalance(balance); book.setStatus(status); book.setUserId(userId); return accountRepository.save(account); } public Boolean deleteAccount (String id) { return accountRepository.deleteById(id); } public Account updateAccountStatus (String Status,String id) { Account account = accountRepository.findAccountById(id); book.setStatus(Status); return accountRepository.save(account); } }
Query.java查询解析相关代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 @Component @AllArgsConstructor public class Query implements GraphQLQueryResolver { private UserRepository userRepo; private AccountRepo accountRepo; public User findUserById (Long id) { return userRepo.findUserById(id); } public List<User> findAllUsers () { return userRepo.findAll(); } public Long countUsers () { return userRepo.count(); } public List<Account> findAllAccounts () { return accountRepo.findAll(); } public Long countAccounts () { return accountRepo.count(); } }
4. 建立GraphQL文件 在resource下面建立graphq文件夹,在里面包含两个文件root.graphqls和schema.graphqls
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 type Query { findUserById(id: String!): User findAllUsers: [ User] ! countAuthors: Long! findAllAccount: [ Account] ! countAccounts: Long! } String balance, String status, String userId type Mutation { newUser(name: String!, age: int!, identification: String!) : Author! newAccount(balance: String!, status: String!, userId: Long!) : Account! saveAccount(input: AccountInput!) : Account! deleteAccount(id: String!) : Boolean updateAccountStatus(status: String!, id: String!) : Account! }
定义规则前后端都强类型一致的文件,并且要明确注释信息 ,前后端不用产生歧义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 type User { id: String! createdTime: String name: String age: int identification: String accounts: [ Account] } input AccountInput { balance: String! status: String! userId: String! } type Account { id: String! balance: String! status: String! userId: User }
5 测试 我们将程序跑起来,进行一个简单的测试.GraphQL自带了一个界面化的测试工具并且还有代码提醒功能,这全都归结于我们前期对于root.graphqls和schema.graphqls的定义
在http://localhost:8080/graphiql 界面进行测试界面如下