Graphql Go 基于Golang实践

【枍酒】-妈呀LOFTER上看到的的 我看了这个好他妈惊

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 这里不解释了。

根据上面一段示例知道,在使用时需要有SchemaQuery一起解析生成查询文档对象后,使用查询器对查询文档对象进行解析。

第一步

一般推荐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查询,使用typeinputenum的参数方式,实现CRUD操作。微服务采用restfulgraphql两种方式进行开发,两者相辅相成,比如:上传、websocket等一些接口混用的模式。
示例代码:GitHub

示例环境 go:1.13,编辑器vscode
GraphQL 官网实现 graphql-js 这个的库,使用 typescript强类型代码编写。
如果不会语法,建议先看看 graphql-js 基础SDL文件编写。

示例目录文件说明

题外学习

Golang

JavaScript

Java

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,492评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,048评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,927评论 0 358
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,293评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,309评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,024评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,638评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,546评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,073评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,188评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,321评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,998评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,678评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,186评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,303评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,663评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,330评论 2 358

推荐阅读更多精彩内容