GraphQL
GraphQL
既是一种用于API
的查询语言也是一个满足你数据查询的运行时。GraphQL
对你的API
中的数据提供了一套易于理解的完整描述,使得客户端能够准确地获得它需要的数据,而且没有任何冗余,也让 API 更容易地随着时间推移而演进,还能用于构建强大的开发者工具。
GraphQL技术在 NodeJS
的服务端中常常会使用,而 Golang
作为高性能的现代语言在 web
服务器开发中也有很高的使用率,特别是在云原生的构建上。
根据 GraphQL 中文官网代码 中找到graphql-go
:一个 Go/Golang 的 GraphQL 实现。
这个库还封装 graphql-go-handler:通过HTTP请求处理GraphQL查询的中间件。
package main
import (
"encoding/json"
"fmt"
"log"
"github.com/graphql-go/graphql"
)
func main() {
// Schema
fields := graphql.Fields{
"hello": &graphql.Field{
Type: graphql.String,
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
return "world", nil
},
},
}
rootQuery := graphql.ObjectConfig{Name: "RootQuery", Fields: fields}
schemaConfig := graphql.SchemaConfig{Query: graphql.NewObject(rootQuery)}
schema, err := graphql.NewSchema(schemaConfig)
if err != nil {
log.Fatalf("failed to create new schema, error: %v", err)
}
// Query
query := `
{
hello
}
`
params := graphql.Params{Schema: schema, RequestString: query}
r := graphql.Do(params)
if len(r.Errors) > 0 {
log.Fatalf("failed to execute graphql operation, errors: %+v", r.Errors)
}
rJSON, _ := json.Marshal(r)
fmt.Printf("%s \n", rJSON) // {“data”:{“hello”:”world”}}
}
开始吧
go
的版本建议1.12以后的,根据创建的项目都应该有一个go.mod
进行依赖包的管理,说一说go mod 这里不解释了。
根据上面一段示例知道,在使用时需要有Schema
、Query
一起解析生成查询文档对象后,使用查询器对查询文档对象进行解析。
第一步
一般推荐SDL语法的.graphql
文件,更强类型要求需要编写类似以下代码。
// schemaQuery 查询函数路由
var schemaQuery= graphql.NewObject(graphql.ObjectConfig{
Name: graphql.DirectiveLocationQuery,
Description: "查询函数",
Fields: graphql.Fields{
// 简单输出字符串
"hello": &graphql.Field{
Type: graphql.String, // 返回类型
Description: "输出 world", // 解释说明
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
// 根据查询处理函数方法进行返回对应类型的数据值
return "word", nil
},
},
// 参数直接输出
"echo": &graphql.Field{
Type: graphql.String, // 返回类型
Description: "参数直接输出", // 解释说明
Args: graphql.FieldConfigArgument{ // 参数接收
"toEcho": &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.String), // 接收参数类型,表示非空字符串
},
},
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
// 根据查询处理函数方法进行返回对应类型的数据值
return p.Args["toEcho"].(string), nil
},
},
},
})
第二步
进行Schema文档组合
// Schema
var Schema graphql.Schema
Schema, _ = graphql.NewSchema(graphql.SchemaConfig{
Query: schemaQuery, // 查询函数Schema
Mutation: schemaMutation, // 如果有提交函数Schema
})
第三步
获取参数与Schema对应查询函数执行
// ExecuteQuery GraphQL查询器
func ExecuteQuery(params *graphql.Params) *graphql.Result {
params.Schema = schema
return graphql.Do(*params)
}
第四步
在路由入口解析参数,并使用查询
// 请求入口
http.HandleFunc("/graphql", func(res http.ResponseWriter, req *http.Request) {
// JSON格式输出,状态200
res.Header().Add("Content-Type", "application/json; charset=utf-8")
res.WriteHeader(http.StatusOK)
// 解析请求参数,得到Query、Variables、OperationName三个参数
opts := ParseRequestOptions(req) // 需要自己写函数处理得到参数
// 进行graphql查询Query
result := ExecuteQuery(&graphql.Params{ // 使用查询
RequestString: opts.Query,
VariableValues: opts.Variables,
OperationName: opts.OperationName,
Context: req.Context(),
})
// 错误输出
if len(result.Errors) > 0 {
log.Printf("errors: %v", result.Errors)
}
// map转json序列化
buff, _ := json.Marshal(result)
_, _ = res.Write(buff)
})
大致通过上面四步完成,简单使用graphql
进行接口操作。
查询选择字段
符合graphql
的设计是根据对应查询字段出对应于字段的信息,不是查询全部字段才根据字段返回。
应该在获取查询时的字段后对应进行SQL
字段查询。
获取提交查询的字段就比较麻烦,自己处理遍历SelectionSet
得到。
根据大多数问题总结,下面提供两种方法函数解析获取。
// SelectionFieldNames 查询选择字段
func SelectionFieldNames(fieldASTs []*ast.Field) []string{
fieldNames := make([]string, 0)
for _, field := range fieldASTs {
selections := field.SelectionSet.Selections
for _, selection := range selections {
fieldNames = append(fieldNames, selection.(*ast.Field).Name.Value)
}
}
return fieldNames
}
// selectedFieldsFromSelections 提交查询的字段列表
func selectedFieldsFromSelections(params graphql.ResolveParams, selections []ast.Selection) (selected map[string]interface{}, err error) {
selected = map[string]interface{}{}
for _, s := range selections {
switch s := s.(type) {
case *ast.Field:
if s.SelectionSet == nil {
selected[s.Name.Value] = true
} else {
selected[s.Name.Value], err = selectedFieldsFromSelections(params, s.SelectionSet.Selections)
if err != nil {
return
}
}
case *ast.FragmentSpread:
n := s.Name.Value
frag, ok := params.Info.Fragments[n]
if !ok {
err = fmt.Errorf("getSelectedFields: no fragment found with name %v", n)
return
}
selected[s.Name.Value], err = selectedFieldsFromSelections(params, frag.GetSelectionSet().Selections)
if err != nil {
return
}
default:
err = fmt.Errorf("getSelectedFields: found unexpected selection type %v", s)
return
}
}
return
}
最后
通过graphql-go
这个库实现GraphQl查询,使用type
、input
、enum
的参数方式,实现CRUD操作。微服务采用restful
和graphql
两种方式进行开发,两者相辅相成,比如:上传、websocket等一些接口混用的模式。
示例代码:GitHub
示例环境
go:1.13
,编辑器vscode
GraphQL
官网实现 graphql-js 这个的库,使用typescript
强类型代码编写。
如果不会语法,建议先看看graphql-js
基础SDL文件编写。
题外学习
Golang
- graphql-go-GoDoc官网说明
- Golang Gin GraphQL 搭建api框架
JavaScript
- graphql-js-GraphQL官网
- graphql-js-GraphQL中文网
- node基于express的GraphQL API服务器
- GraphQL入门有这一篇就足够了
Java
- GraphQL Java 基于SpringBoot实践