Facebook:如何在Golang中搭建GraphQL?
本文转载自公众号“读芯术”(ID:AI_Discovery)。
多年来,人们一直在使用REST API来满足开发需求,但得完成大量不必要的调用后,开发者才能灵活使用。例如,如果Web和移动设备所需的数据不同,我们还须针对Web和移动设备创建两个不同的端点。
因此,Facebook创建了一种查询语言——GraphQL,该语言可以准确地给出开发者查询的内容,干净利落,也让 API 更容易地随着时间推移而演进,还能用于构建强大的开发者工具。
本文将重点介绍GraphQL的主要功能,以及就API而言它存在的优缺点。文末将展示一个使用Golang的简单程序(已搭建GraphQL)。
什么是GraphQL?
GraphQL是用于API的查询语言,它是服务器端运行时,通过为数据定义的类型系统执行查询。
GraphQL是一种查询语言,适用许多领域,但通常用来在客户端和服务器应用程序之间搭桥。无所谓使用的是哪个网络层,所以可以在客户端和服务器应用程序之间读取和写入数据。(RobinWieruch《GraphQL指南》)
虽然GraphQL是查询语言,但它与数据库没有直接关系,也就是GraphQL不限于任意SQL或是NoSQL的数据库。GraphQL位于客户端和服务器端,通过API连接/访问。开发这种查询语言的目的之一是通过提供所需的数据来促进后端、前端或移动应用程序之间的数据通信。
![Facebook:如何在Golang中搭建GraphQL?](https://s5.51cto.com/oss/202010/27/301ab4124b92f415934b0b5b6297844a.jpeg)
GraphQL的操作
1. 查询(Query)
查询用于读取或获取值。无论哪种情况,操作都是一个简单的字符串,GraphQL服务器可以解析该字符串并以特定格式的数据进行响应。
你可以使用查询操作从API请求数据。查询描述需要从GraphQL服务器获取的数据,发送查询其实是按字段要求提取数据。(Eve Porcello、Alex Banks著《学习GraphQL》)
![Facebook:如何在Golang中搭建GraphQL?](https://s4.51cto.com/oss/202010/27/625947e2085a805e43f3533b03eb6f4b.jpeg)
2. 模式(Schema)
GraphQL使用Schema描述数据图的形状。这样的Schema定义类型的层次结构,依托的是从后端数据存储区填充的字段,也准确表示客户端可以对数据图执行哪些查询和突变。
3. 分解器(Resolver)
分解器是负责为Schema单一字段填充数据的功能。它可以用你定义的任何方式填充该数据,例如从后端数据库或第三方API提取数据。
4. 突变(Mutation)
修改数据存储中的数据并返回一个值,它可用于插入、更新或删除数据。
突变与查询原理相同:它具有字段和对象、参数和变量、片段和操作名称,以及返回结果的指令和嵌套对象。(Robin Wieruch著《GraphQL之路》)
![Facebook:如何在Golang中搭建GraphQL?](https://s2.51cto.com/oss/202010/27/90e54087ac0fabf02672402c3ff0ae41.jpeg)
5. 订阅(Subscription)
将数据从服务器推送到客户端的方法是选择侦听来自服务器的实时消息。
GraphQL的订阅来自Facebook的真实用例。开发团队希望找到一种方法,不刷新页面就能实时显示发文获得的有效点赞(Live Likes)。(Eve Porcello、Alex Banks著《学习GraphQL》)
![Facebook:如何在Golang中搭建GraphQL?](https://s3.51cto.com/oss/202010/27/cefe2218cfb714988af3ec4e290faff7.jpeg)
GraphQL的优势与劣势
![Facebook:如何在Golang中搭建GraphQL?](https://s4.51cto.com/oss/202010/27/b29a6a578bd1bb633cf1f4eff63eaaad.jpeg)
1. 优势
(1) 开发迅速
来看一个案例:如何得到图书借阅者的数据。在视图中,首先我要显示书籍列表,书籍列表菜单显示中出现一个借阅者的列表。在REST API中,需要创建新的端点以返回图书清单,再创建一个新的端点以返回每本书的借阅人。
![Facebook:如何在Golang中搭建GraphQL?](https://s5.51cto.com/oss/202010/27/da99d6b71ad2161ccb19ad6924e8a282.jpeg)
与REST API不同,GraphQL中仅使用一个端点就可以返回书籍列表和借阅者列表了。
![Facebook:如何在Golang中搭建GraphQL?](https://s3.51cto.com/oss/202010/27/b5cb51dcc5ebee0f2818ba24754ed5ca.jpeg)
使用以下示例GraphQL查询:
![Facebook:如何在Golang中搭建GraphQL?](https://s3.51cto.com/oss/202010/27/dfbccf87e5220f5a036d149b8e3ca198.jpeg)
(2) 灵活性
来看一个案例:如何获取书籍详细信息。在网络视图上,我想展示书籍详细信息,例如名称、价格和介绍。在REST API中需要创建一个新的端点以返回名称、价格、介绍等的书籍详细信息。
![Facebook:如何在Golang中搭建GraphQL?](https://s5.51cto.com/oss/202010/27/8c573fc830c6add370f3cf82719d175d.jpeg)
如果在移动端查看时,只想展示图书详细信息中的名称和价格怎么办?如果使用与Web视图相同的端点,则会浪费介绍的数据。所以需要更改该端点内部的现有逻辑,或创建一个新的端点。
![Facebook:如何在Golang中搭建GraphQL?](https://s4.51cto.com/oss/202010/27/33a5567384bc1073b10c926d5be37487.jpeg)
与REST API不同,GraphQL中仅使用一个端点即可按照Web或移动设备的需求返回书籍详细信息。在GraphQL中,只需更改查询。
(3) 维护简单,易于使用
- Rest API:如果客户端需要其他数据,通常需要添加一个新端点或更改一个现有端点。
- GraphQL:客户只需要更改查询。
2. 缺点
- 处理文件上传:GraphQL规范中没有关于文件上传的内容,并且突变不接受参数中的文件。
- 简单的API:如果你的API非常简单,那GraphQL只会使其复杂,所以使用REST API可能会更好。
代码实现
实现过程使用了Golang编程语言,这里是项目架构:
![Facebook:如何在Golang中搭建GraphQL?](https://s2.51cto.com/oss/202010/27/73127cac8314ac73453f1f654978a3f5.jpeg)
在依赖版本和依赖管理功能上使用的是go模块。用graphql-go来支持查询、突变和订阅;用graphql-go-handler来支持处理器。此时,我将创建一个简单的程序,这里使用GraphQL为详细书目创建CRUD。步骤如下:
先新建一个环境文件夹,然后新建一个名为connection.yml的文件:
- app:
- name: "GraphQL Test"
- debug: true
- port: "8080"
- host: "localhost"
- service: "http"
- context:
- timeout: 2
- databases:
- mongodb:
- name: "local_db"
- connection: "mongodb://root:root@localhost:27017"
然后创建一个架构文件夹,创建名为databaseConfiguration.go、environmentConfiguration.go和model.go的文件。这个文件夹用来配置数据库并从connection.yml读取数据。
(1) databaseConfiguration.go
- package infrastructureimport(
- "context"
- "go.mongodb.org/mongo-driver/mongo"
- "go.mongodb.org/mongo-driver/mongo/options"
- "log"
- )var Mongodb *mongo.Databasefunc(e *Environment) InitMongoDB()(db *mongo.Database, err error) {
- clientOptions :=options.Client().ApplyURI(e.Databases["mongodb"].Connection)
- client, err := mongo.Connect(context.TODO(),clientOptions)
- err = client.Ping(context.TODO(), nil)
- if err != nil {
- return db, err
- }
- Mongodb = client.Database(e.Databases["mongodb"].Name)
- log.Println("Mongodb Ready!!!")
- return db, err
- }
(2) environmentConfiguration.go
- package infrastructureimport(
- "io/ioutil"
- "log"
- "os"
- "path"
- "runtime""gopkg.in/yaml.v2"
- )func(env *Environment) SetEnvironment() {
- _, filename, _, _ := runtime.Caller(1)
- env.path = path.Join(path.Dir(filename),"environment/Connection.yml")
- _, err := os.Stat(env.path)
- if err != nil {
- panic(err)
- return
- }
- }func(env *Environment) LoadConfig() {
- content, err :=ioutil.ReadFile(env.path)
- if err != nil {
- log.Println(err)
- panic(err)
- }
- err =yaml.Unmarshal([]byte(string(content)), env)
- if err != nil {
- log.Println(err)
- panic(err)
- }
- if env.App.Debug == false {
- log.SetOutput(ioutil.Discard)
- }
- log.Println("Config load successfully!")
- return
- }
(3) model.go
- package infrastructuretypeapp struct{
- Appname string `yaml:"name"`
- Debug bool `yaml:"debug"`
- Port string `yaml:"port"`
- Service string `yaml:"service"`
- Host string `yaml:"host"`
- }type database struct {
- Name string `yaml:"name"`
- Connection string`yaml:"connection"`
- }type Environment struct {
- App app `yaml:"app"`
- Databases map[string]database`yaml:"databases"`
- path string
- }
第三,创建一个书目文件夹,创建如下文件:
![Facebook:如何在Golang中搭建GraphQL?](https://s5.51cto.com/oss/202010/27/c9b469980368fda378c80824c63c2d15.jpeg)
model.go:
- package
- package booktypeBook struct {
- Name string
- Price string
- Description string
- } booktypeBook struct { Name string Price string Description string}
resolver.go:
- package bookimport(
- "context""github.com/graphql-go/graphql"
- )var productType = graphql.NewObject(
- graphql.ObjectConfig{
- Name: "Book",
- Fields: graphql.Fields{
- "name": &graphql.Field{
- Type: graphql.String,
- },
- "price":&graphql.Field{
- Type: graphql.String,
- },
- "description":&graphql.Field{
- Type: graphql.String,
- },
- },
- },
- )var queryType = graphql.NewObject(
- graphql.ObjectConfig{
- Name: "Query",
- Fields: graphql.Fields{
- "book":&graphql.Field{
- Type: productType,
- Description: "Get bookby name",
- Args: graphql.FieldConfigArgument{
- "name":&graphql.ArgumentConfig{
- Type: graphql.String,
- },
- },
- Resolve: func(pgraphql.ResolveParams) (interface{}, error) {
- var result interface{}
- name, ok :=p.Args["name"].(string)
- if ok {
- // Find product
- result =GetBookByName(context.Background(), name)
- }
- return result, nil
- },
- },
- "list":&graphql.Field{
- Type: graphql.NewList(productType),
- Description: "Get booklist",
- Args: graphql.FieldConfigArgument{
- "limit":&graphql.ArgumentConfig{
- Type: graphql.Int,
- },
- },
- Resolve: func(paramsgraphql.ResolveParams) (interface{}, error) {
- var result interface{}
- limit, _ :=params.Args["limit"].(int)
- result =GetBookList(context.Background(), limit)
- return result, nil
- },
- },
- },
- })var mutationType =graphql.NewObject(graphql.ObjectConfig{
- Name: "Mutation",
- Fields: graphql.Fields{
- "create":&graphql.Field{
- Type: productType,
- Description: "Create newbook",
- Args: graphql.FieldConfigArgument{
- "name":&graphql.ArgumentConfig{
- Type:graphql.NewNonNull(graphql.String),
- },
- "price":&graphql.ArgumentConfig{
- Type:graphql.NewNonNull(graphql.String),
- },
- "description":&graphql.ArgumentConfig{
- Type:graphql.NewNonNull(graphql.String),
- },
- },
- Resolve: func(paramsgraphql.ResolveParams) (interface{}, error) {
- book := Book{
- Name: params.Args["name"].(string),
- Price: params.Args["price"].(string),
- Description:params.Args["description"].(string),
- }
- if err := InsertBook(context.Background(), book); err != nil {
- return nil, err
- }return book, nil
- },
- },"update":&graphql.Field{
- Type: productType,
- Description: "Update bookby name",
- Args: graphql.FieldConfigArgument{
- "name":&graphql.ArgumentConfig{
- Type:graphql.NewNonNull(graphql.String),
- },
- "price":&graphql.ArgumentConfig{
- Type: graphql.String,
- },
- "description":&graphql.ArgumentConfig{
- Type: graphql.String,
- },
- },
- Resolve: func(paramsgraphql.ResolveParams) (interface{}, error) {
- book := Book{}
- if name, nameOk := params.Args["name"].(string); nameOk {
- book.Name = name
- }
- if price, priceOk := params.Args["price"].(string); priceOk {
- book.Price = price
- }
- if description, descriptionOk :=params.Args["description"].(string); descriptionOk {
- book.Description = description
- }if err :=UpdateBook(context.Background(), book); err != nil {
- return nil, err
- }
- return book, nil
- },
- },"delete": &graphql.Field{
- Type: productType,
- Description: "Delete bookby name",
- Args: graphql.FieldConfigArgument{
- "name":&graphql.ArgumentConfig{
- Type:graphql.NewNonNull(graphql.String),
- },
- },
- Resolve: func(paramsgraphql.ResolveParams) (interface{}, error) {
- name, _ :=params.Args["name"].(string)
- if err := DeleteBook(context.Background(), name); err != nil {
- return nil, err
- }
- return name, nil
- },
- },
- },
- })// schema
- var Schema, _ = graphql.NewSchema(
- graphql.SchemaConfig{
- Query: queryType,
- Mutation: mutationType,
- },
- )
repository.go:
- package bookimport(
- "context"
- "log""graphql/infrastructure""go.mongodb.org/mongo-driver/bson"
- "go.mongodb.org/mongo-driver/mongo/options"
- )funcGetBookByName(ctxcontext.Context, name string) (result interface{}){
- var book Book
- data :=infrastructure.Mongodb.Collection("booklist").FindOne(ctx,bson.M{"name": name})
- data.Decode(&book)
- return book
- }funcGetBookList(ctxcontext.Context, limit int) (result interface{}){
- var book Book
- var books []Bookoption := options.Find().SetLimit(int64(limit))cur, err:= infrastructure.Mongodb.Collection("booklist").Find(ctx, bson.M{},option)
- defer cur.Close(ctx)
- if err != nil {
- log.Println(err)
- return nil
- }
- for cur.Next(ctx) {
- cur.Decode(&book)
- books = append(books, book)
- }
- return books
- }funcInsertBook(ctxcontext.Context, book Book) error {
- _, err :=infrastructure.Mongodb.Collection("booklist").InsertOne(ctx, book)
- return err
- }funcUpdateBook(ctxcontext.Context, book Book) error {
- filter := bson.M{"name":book.Name}
- update := bson.M{"$set":book}
- upsertBool := true
- updateOption := options.UpdateOptions{
- Upsert: &upsertBool,
- }
- _, err :=infrastructure.Mongodb.Collection("booklist").UpdateOne(ctx, filter,update, &updateOption)
- return err
- }funcDeleteBook(ctxcontext.Context, name string) error {
- _, err :=infrastructure.Mongodb.Collection("booklist").DeleteOne(ctx,bson.M{"name": name})
- return err
- }
response.go:
- package bookimport(
- "encoding/json"
- "net/http"
- "time"
- )type SetResponsestruct {
- Status string `json:"status"`
- Data interface{} `json:"data,omitempty"`
- AccessTime string `json:"accessTime"`
- }funcHttpResponseSuccess(w http.ResponseWriter, r *http.Request, data interface{}){
- setResponse := SetResponse{
- Status: http.StatusText(200),
- AccessTime: time.Now().Format("02-01-2006 15:04:05"),
- Data: data}
- response, _ :=json.Marshal(setResponse)
- w.Header().Set("Content-Type", "Application/json")
- w.WriteHeader(200)
- w.Write(response)
- }funcHttpResponseError(w http.ResponseWriter, r *http.Request, data interface{},code int) {
- setResponse := SetResponse{
- Status: http.StatusText(code),
- AccessTime: time.Now().Format("02-01-2006 15:04:05"),
- Data: data}
- response, _ :=json.Marshal(setResponse)
- w.Header().Set("Content-Type", "Application/json")
- w.WriteHeader(code)
- w.Write(response)
- }
routes.go:
- package bookimport(
- "github.com/go-chi/chi"
- "github.com/go-chi/chi/middleware"
- "github.com/graphql-go/handler"
- )funcRegisterRoutes(r *chi.Mux) *chi.Mux {
- /* GraphQL */
- graphQL := handler.New(&handler.Config{
- Schema: &Schema,
- Pretty: true,
- GraphiQL: true,
- })
- r.Use(middleware.Logger)
- r.Handle("/query", graphQL)
- return r
- }
最后,创建名为 main.go的文件。
main.go:
- package mainimport(
- "github.com/go-chi/chi"
- "graphql/book"
- "graphql/infrastructure"
- "log"
- "net/http"
- "net/url"
- )funcmain() {
- routes := chi.NewRouter()
- r := book.RegisterRoutes(routes)
- log.Println("Server ready at 8080")
- log.Fatal(http.ListenAndServe(":8080", r))
- }funcinit() {
- val := url.Values{}
- val.Add("parseTime", "1")
- val.Add("loc", "Asia/Jakarta")
- env := infrastructure.Environment{}
- env.SetEnvironment()
- env.LoadConfig()
- env.InitMongoDB()
- }
运行程序的结果如下:
![Facebook:如何在Golang中搭建GraphQL?](https://s3.51cto.com/oss/202010/27/49617c6a61b4c3d0fe03bbb0c9e83357.jpeg)
GraphQL有很多优点,但事实证明,与REST API相比,GraphQL处理文件上传和简单API的性能表现有所不足。因此,我们必须首先了解要构建的系统,是否适合将GraphQL用作应用程序的设计架构。
相关文章
- 从本体论开始说起——运营商关系图谱的构建及应用
- 如何成为一名数据科学家?
- 从未见过的堂兄杀了人,你的DNA是关键证据
- 20个安全可靠的免费数据源,各领域数据任你挑
- 20个安全可靠的免费数据源,各领域数据任你挑
- 阿里云李飞飞:All in Cloud时代,云原生数据库优势明显
- 基于Hadoop生态系统的一高性能数据存储格式CarbonData(性能篇)
- 大数据告诉你:10年漫威,到底有多少角色
- TigerGraph:实时图数据库助力金融风控升级
- Splunk利用Splunk Connected Experiences和Splunk Business Flow 扩大数据访问
- 大数据开发常见的9种数据分析手段
- 以免在景区看人,我爬了5W条全国景点门票数据...
- 【实战解析】基于HBase的大数据存储在京东的应用场景
- 数据科学家告诉你哪些计算机科学书籍是你应该看的
- Kafka作为大数据的核心技术,你了解多少?
- Spring Boot 整合 Redis 实现缓存操作
- 大数据学习必须掌握的五大核心技术有哪些?
- 基于Antlr在Apache Flink中实现监控规则DSL化的探索实践
- 甲骨文再次被Gartner评为分析型数据管理解决方案魔力象限领导者
- 爬取吴亦凡微博102118条转发数据,扒一扒流量的真假