不久之前寫過一篇『從 graphql-go 轉換到 gqlgen』,目前團隊舊有的專案還是繼續用 graphql-go 來撰寫,不過之後需求量越來越大,維護 graphql-go 就越來越困難,故有在想怎麼把 gqlgen 跟 graphql-go 相容在一起,那就是把這兩個套件想成不同的服務,再透過 Gateway 方式完成 single data graph。至於怎麼選擇 GraphQL Gateway 套件,最容易的方式就是使用 @apollo/gateway,但是由於個人比較偏好 Go 語言的解決方案,就稍微找看看有無人用 Go 實現了 Gateway,後來找到 nautilus/gateway,官方有提供文件以及教學 Blog 可以供開發者參考。底下會教大家使用 nautilus/gateway 將兩個不同的服務串接在一起。
Continue reading “使用 GraphQL Gateway 串接多個 Data Schema”Tag: GraphQL
為什麼要學 GraphQL?
身為網站工程師,您不能不知道什麼是 GraphQL,這是一個前端跟後端溝通的 API Query 語法,大幅改善了前後端的合作模式,這篇會跟大家介紹為什麼麼要學 GraphQL,以及整理出三大 GraphQL 優勢,讓大家了解跟傳統 RESTful API 有什麼不同。當然不是叫開發者捨棄 RESTful API,而是根據專案的不同,來決定不同的技術 Stack。像是服務跟服務之前您說要用 GraphQL,肯定被打槍,而是要用更輕量的 RESTful API 或 gRPC。好了,底下來說明三點 GraphQL 的優勢。
Continue reading “為什麼要學 GraphQL?”[Go 語言] 從 graphql-go 轉換到 gqlgen
相信各位開發者對於 GraphQL 帶來的好處已經非常清楚,如果對 GraphQL 很陌生的朋友們,可以直接參考之前作者寫的一篇『Go 語言實戰 GraphQL』,內容會講到用 Go 語言實戰 GraphQL 架構,教開發者如何撰寫 GraphQL 測試及一些開發小技巧,不過內容都是以 graphql-go 框架為主。而本篇主題會講為什麼我從 graphql-go 框架轉換到 gqlgen。
Continue reading “[Go 語言] 從 graphql-go 轉換到 gqlgen”Go 語言的 graphQL-go 套件正式支援 Concurrent Resolvers
要在 Go 語言寫 graphQL,大家一定對 graphql-go 不陌生,討論度最高的套件,但是我先說,雖然討論度是最高,但是效能是最差的,如果大家很要求效能,可以先參考此專案,裡面有目前 Go 語言的 graphQL 套件比較效能,有機會在寫另外一篇介紹。最近 graphql-go 的作者把 Concurrent Resolvers 的解法寫了一篇 Issue 來討論,最終採用了 Resolver returns a Thunk 方式來解決 Concurrent 問題,這個 PR 沒有用到額外的 goroutines,使用方式也最簡單
"pullRequests": &graphql.Field{
Type: graphql.NewList(PullRequestType),
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
ch := make(chan []PullRequest)
// Concurrent work via Goroutines.
go func() {
// Async work to obtain pullRequests.
ch <- pullRequests
}()
return func() interface{} {
return <-ch
}, nil
},
},
使用方式
先用一個簡單例子來解釋之前的寫法會是什麼形式
package main
import (
"encoding/json"
"fmt"
"log"
"time"
"github.com/graphql-go/graphql"
)
type Foo struct {
Name string
}
var FieldFooType = graphql.NewObject(graphql.ObjectConfig{
Name: "Foo",
Fields: graphql.Fields{
"name": &graphql.Field{Type: graphql.String},
},
})
type Bar struct {
Name string
}
var FieldBarType = graphql.NewObject(graphql.ObjectConfig{
Name: "Bar",
Fields: graphql.Fields{
"name": &graphql.Field{Type: graphql.String},
},
})
// QueryType fields: `concurrentFieldFoo` and `concurrentFieldBar` are resolved
// concurrently because they belong to the same field-level and their `Resolve`
// function returns a function (thunk).
var QueryType = graphql.NewObject(graphql.ObjectConfig{
Name: "Query",
Fields: graphql.Fields{
"concurrentFieldFoo": &graphql.Field{
Type: FieldFooType,
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
type result struct {
data interface{}
err error
}
ch := make(chan *result, 1)
go func() {
defer close(ch)
time.Sleep(1 * time.Second)
foo := &Foo{Name: "Foo's name"}
ch <- &result{data: foo, err: nil}
}()
r := <-ch
return r.data, r.err
},
},
"concurrentFieldBar": &graphql.Field{
Type: FieldBarType,
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
type result struct {
data interface{}
err error
}
ch := make(chan *result, 1)
go func() {
defer close(ch)
time.Sleep(1 * time.Second)
bar := &Bar{Name: "Bar's name"}
ch <- &result{data: bar, err: nil}
}()
r := <-ch
return r.data, r.err
},
},
},
})
func main() {
schema, err := graphql.NewSchema(graphql.SchemaConfig{
Query: QueryType,
})
if err != nil {
log.Fatal(err)
}
query := `
query {
concurrentFieldFoo {
name
}
concurrentFieldBar {
name
}
}
`
result := graphql.Do(graphql.Params{
RequestString: query,
Schema: schema,
})
b, err := json.Marshal(result)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s", b)
/*
{
"data": {
"concurrentFieldBar": {
"name": "Bar's name"
},
"concurrentFieldFoo": {
"name": "Foo's name"
}
}
}
*/
}
接著看看需要多少時間來完成執行
$ time go run examples/concurrent-resolvers/main.go | jq
{
"data": {
"concurrentFieldBar": {
"name": "Bar's name"
},
"concurrentFieldFoo": {
"name": "Foo's name"
}
}
}
real 0m4.186s
user 0m0.508s
sys 0m0.925s
總共花費了四秒,原因是每個 resolver 都是依序執行,所以都需要等每個 goroutines 執行完成才能進入到下一個 resolver,上面例子該如何改成 Concurrent 呢,很簡單,只要將 return 的部分換成
return func() (interface{}, error) {
r := <-ch
return r.data, r.err
}, nil
執行時間如下
$ time go run examples/concurrent-resolvers/main.go | jq
{
"data": {
"concurrentFieldBar": {
"name": "Bar's name"
},
"concurrentFieldFoo": {
"name": "Foo's name"
}
}
}
real 0m1.499s
user 0m0.417s
sys 0m0.242s
從原本的 4 秒多,變成 1.5 秒,原因就是兩個 resolver 的 goroutines 會同時執行,最後才拿結果。
心得
有了這功能後,比較複雜的 GraphQL 語法,就可以用此方式加速執行時間。作者也用 MongoDB + graphql 寫了一個範例,大家可以參考看看。
Go 語言實戰 GraphQL
