Graphql Go 基於 Golang 實踐

【導讀】Go 語言如何使用 Graphql 操作數據庫?本文做了詳細介紹。

GraphQL

GraphQL 既是一種用於 API 的查詢語言也是一個滿足你數據查詢的運行時。GraphQL 對你的 API 中的數據提供了一套易於理解的完整描述,使得客戶端能夠準確地獲得它需要的數據,而且沒有任何冗餘,也讓 API 更容易地隨着時間推移而演進,還能用於構建強大的開發者工具。

基於 node 的服務端開發中,GraphQL 技術較爲成熟常用。Golang 作爲高性能的現代語言在 web 服務器開發中也有很高的使用率,配合使用真香。

根據 [GraphQL 官網代碼] 中找到graphql-go:一個 Go/Golang 的 GraphQL 實現。
這個庫還封裝 graphql-go-handler(https://github.com/graphql-go/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(https://studygolang.com/articles/24119?fr=sidebar) 這裏不解釋了。

根據上面一段示例知道,在使用時需要有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 等一些接口混用的模式。
示例代碼:https://github.com/TsMask/graphql-server-go

示例環境 go:1.13,編輯器vscode
GraphQL 官網實現JavaScript的,使用typescript強類型代碼編寫。
如果不會語法,建議先看看JavaScript基礎 SDL 文件編寫。

轉自:

juejin.cn/post/6844904056289230856

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/J9cqMwCXjL0iI8yKzG-DBA