本文转载自公众号《核心阅读》(ID:AI_Discovery)。多年来,人们一直在使用RESTAPI来满足他们的开发需求,但开发人员必须进行大量不必要的调用才能灵活使用它们。例如,如果Web和移动需要不同的数据,我们还必须为Web和移动创建两个不同的端点。因此,Facebook创造了一种查询语言——GraphQL,它可以准确地给出开发者查询的内容,干净整洁,也使得API更容易随时间演进,也可以用来构建强大的开发工具。本文将重点介绍GraphQL的主要特性,以及它在API方面的优缺点。在文章的最后,将展示一个使用Golang(已构建GraphQL)的简单程序。什么是GraphQL?GraphQL是一种用于API的查询语言,它是一种服务器端运行时,通过为数据定义的类型系统执行查询。GraphQL是一种可用于许多领域的查询语言,但通常用于桥接客户端和服务器应用程序。使用哪个网络层并不重要,因此可以在客户端和服务器应用程序之间读取和写入数据。(RobinWieruch《GraphQL指南》)虽然GraphQL是一种查询语言,但它与数据库没有直接的关系,也就是说,GraphQL并不局限于任何SQL或NoSQL数据库。GraphQL在客户端和服务器端,通过API连接/访问。开发这种查询语言的目的之一是通过提供所需的数据来促进后端、前端或移动应用程序之间的数据通信。GraphQL的操作1.查询(Query)查询用于读取或获取值。在任何一种情况下,该操作都是一个简单的字符串,GraphQL服务器可以解析该字符串并以特定格式的数据进行响应。您可以使用查询操作从API请求数据。查询描述了需要从GraphQL服务器获取的数据。发送查询实际上是根据字段需求提取数据。(EvePorcello,AlexBanks《学习GraphQL》)2.SchemaGraphQL使用Schema来描述数据图的形状。这样的模式通过从后端数据存储填充的字段定义了类型的层次结构,并且还准确地表示客户端可以在数据图上执行哪些查询和变更。3.ResolverResolver是负责为Schema的单个字段填充数据的函数。它可以以您定义的任何方式填充此数据,例如从后端数据库或第三方API中提取数据。4.变异(Mutation)修改数据存储中的数据并返回一个值,它可用于插入、更新或删除数据。Mutation的工作方式与查询相同:它具有字段和对象、参数和变量、片段和操作名称以及返回结果的指令和嵌套对象。(RobinWieruchby《GraphQL之路》)5.订阅(Subscription)从服务端向客户端推送数据的方法是选择监听来自服务端的实时消息。来自Facebook真实用例的GraphQL订阅。开发组希望找到一种方式,在不刷新页面的情况下,实时显示发帖获得的有效点赞数(LiveLikes)。(作者:EvePorcello和AlexBanks《学习GraphQL》)GraphQL的优缺点1.优点(1)开发快速看一个案例:如何获取图书借阅者的数据。在视图中,首先我要显示图书列表,图书列表菜单显示中出现借阅者列表。在RESTAPI中,需要创建一个新的端点来返回图书列表,以及一个新的端点来返回每本书的借阅者。与RESTAPI不同,GraphQL仅使用一个端点来返回图书列表和借书人列表。使用以下示例GraphQL查询:(2)灵活性让我们看一个案例:如何获取书籍详细信息。在web视图中,我想显示书籍的详细信息,如名称、价格和描述。需要在RESTAPI中创建一个新端点以返回书籍详细信息,例如名称、价格、描述等。如果您只想在移动设备上查看书籍详细信息中显示名称和价格怎么办?如果你使用与web视图相同的端点,你将浪费呈现的数据。因此,您需要更改该端点内的现有逻辑,或创建一个新逻辑。与RESTAPI不同,GraphQL中仅使用一个端点来返回Web或移动设备所需的书籍详细信息。在GraphQL中,只需更改查询。(3)易于维护和易于使用的RestAPI:如果客户端需要额外的数据,通常需要添加一个新的端点或者改变一个已有的端点。GraphQL:客户只需要改变查询。2.缺点处理文件上传:GraphQL规范中没有关于文件上传的任何内容,并且mutations不接受参数中的文件。简单的API:如果你的API非常简单,GraphQL只会让它复杂化,所以你最好使用RESTAPI。代码实现过程使用Golang编程语言,这里是项目架构:go模块用于依赖版本和依赖管理功能。使用graphql-go支持查询、变异和订阅;使用graphql-go-handler来支持处理器。此时,我将创建一个简单的程序,这里使用GraphQL为详细的参考书目创建CRUD。步骤如下:首先新建一个环境文件夹,然后新建一个名为connection.yml的文件:app:name:"GraphQLTest"debug:trueport:"8080"host:"localhost"service:"http"context:timeout:2databases:mongodb:name:"local_db"connection:"mongodb://root:root@localhost:27017"然后创建一个schema文件夹,创建名为databaseConfiguration.go、environmentConfiguration.go和model.go的文件。该文件夹用于配置数据库和从connection.yml中读取数据。(1)databaseConfiguration.gopackageinfrastructureimport("context""go.mongodb.org/mongo-driver/mongo""go.mongodb.org/mongo-driver/mongo/options""log")varMongodb*mongo.Databasefunc(e*环境)InitMongoDB()(db*mongo.Database,errerror){clientOptions:=options.Client().ApplyURI(e.Databases["mongodb"].Connection)client,err:=mongo.Connect(context.TODO(),clientOptions)err=client.Ping(context.TODO(),nil)iferr!=nil{returndb,err}Mongodb=client.Database(e.Databases["mongodb"].Name)log.Println("MongodbReady!!!")returndb,err}(2)environmentConfiguration.gopackageinfrastructureimport("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)iferr!=nil{panic(err)return}}func(env*Environment)LoadConfig(){content,err:=ioutil.ReadFile(env.path)iferr!=nil{log.Println(err)panic(err)}err=yaml.Unmarshal([]byte(string(content)),env)iferr!=nil{log.Println(err)panic(err)}ifenv.App.Debug==false{log.SetOutput(ioutil.Discard)}log.Println("配置加载成功!")return}(3)model.gopackageinfrastructuretypeappstruct{Appnamestring`yaml:"name"`Debugbool`yaml:"debug"`Portstring`yaml:"port"`Servicestring`yaml:"service"`Hoststring`yaml:"host"`}typedatabasestruct{Namestring`yaml:"name"`Connectionstring`yaml:"connection"`}typeEnvironmentstruct{Appapp`yaml:"app"`Databasesmap[string]database`yaml:"databases"`pathstring}第三,创建一个书目文件夹,创建如下文件:model.go:packagepackagebooktypeBookstruct{NamestringPricestringDescriptionstring}booktypeBookstruct{NamestringPricestringDescriptionstring}resolver.go:packagebookimport("context""github.com/graphql-go/graphql")varproductType=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,},},},)varqueryType=graphql.NewObject(graphql.ObjectConfig{Name:"查询",Fields:graphql.Fields{"book":&graphql.Field{Type:productType,Description:"Getbookbyname",Args:graphql.FieldConfigArgument{"name":&graphql.ArgumentConfig{Type:graphql.String,},},解析:func(pgraphql.ResolveParams)(interface{},error){varresultinterface{}name,ok:=p.Args["name"].(string)ifok{//Findproductresult=GetBookByName(context.Background(),name)}returnresult,nil},},"list":&graphql.Field{Type:graphql.NewList(productType),Description:"Getbooklist",Args:graphql.FieldConfigArgument{"lim它":&graphql.ArgumentConfig{Type:graphql.Int,},},Resolve:func(paramsgraphql.ResolveParams)(interface{},error){varresultinterface{}limit,_:=params.Args["limit"]。(int)result=GetBookList(context.Background(),limit)returnresult,nil},},},})varmutationType=graphql.NewObject(graphql.ObjectConfig{Name:"Mutation",Fields:graphql.Fields{"创建":&graphql.Field{Type:productType,Description:"Createnewbook",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)(接口{},error){book:=Book{Name:params.Args["name"].(string),Price:params.Args["price"].(string),Description:params.Args["描述"].(string),}iferr:=InsertBook(context.Background(),book);err!=nil{returnnil,err}returnbook,nil},},"update":&graphql.Field{Type:productType,描述:"Updatebookbyname",Args:graphql.FieldConfigArgument{"name":&graphql.ArgumentConfig{Type:graphql.NewNonNull(graphql.String),},"price":&graphql.ArgumentConfig{Type:graphql.String,},“描述”:&graphql.ArgumentConfig{Type:graphql.String,},},Resolve:func(paramsgraphql.ResolveParams)(interface{},error){book:=Book{}ifname,nameOk:=params.Args["name"].(string);nameOk{book.Name=name}ifprice,priceOk:=params.Args["price"].(string);priceOk{book.Price=price}ifdescription,descriptionOk:=params.Args[“描述”].(string);descriptionOk{book.Description=description}iferr:=UpdateBook(context.Background(),book);err!=nil{returnnil,err}returnbook,nil},},"删除":&graphql.Field{Type:productType,Description:"Deletebookbyname",Args:graphql.FieldConfigArgument{"name":&graphql.ArgumentConfig{Type:graphql.NewNonNull(graphql.String),},},Resolve:func(paramsgraphql.ResolveParams)(接口{},错误){name,_:=params.Args["name"].(string)iferr:=DeleteBook(context.Background(),name);err!=nil{returnnil,err}returnname,nil},},},})//schemavarSchema,_=graphql.NewSchema(graphql.SchemaConfig{Query:queryType,Mutation:mutationType,},)repository.go:packagebookimport("context""log""graphql/infrastructure""go.mongodb.org/mongo-driver/bson""go.mongodb.org/mongo-driver/mongo/options")funcGetBookByName(ctxcontext.Context,namestring)(resultinterface{}){varbookBookdata:=infrastructure.Mongodb.Collection("booklist").FindOne(ctx,bson.M{"name":name})data.Decode(&book)returnbook}funcGetBookList(ctxcontext.Context,limitint)(resultinterface{}){varbookBookvarbooks[]Bookoption:=options.Find().SetLimit(int64(limit))cur,err:=infrastructure.Mongodb.Collection("booklist").Find(ctx,bson.M{},option)defercur.Close(ctx)iferr!=nil{log.Println(err)returnil}forcur.Next(ctx){cur.Decode(&book)books=append(books,book)}returnbooks}funcInsertBook(ctxcontext.Context,bookBook)error{_,err:=infrastructure.Mongodb.Collection("booklist").InsertOne(ctx,book)returnerr}funcUpdateBook(ctxcontext.Context,bookBook)error{filter:=bson.M{"name":book.Name}update:=bson.M{"$set":book}upsertBool:=trueupdateOption:=options.UpdateOptions{Upsert:&upsertBool,}_,err:=infrastructure.Mongodb.Collection("booklist").UpdateOne(ctx,filter,update,&updateOption)returnerr}funcDeleteBook(ctxcontext.Context,namestring)error{_,err:=infrastructure.Mongodb.Collection("booklist").DeleteOne(ctx,bson.M{"name":name})returnerr}response.go:packagebookimport("编码/json""net/http""time")typeSetResponsestruct{Statusstring`json:"status"`Datainterface{}`json:"data,omitempty"`AccessTimestring`json:"accessTime"`}funcHttpResponseSuccess(whttp.ResponseWriter,r*http.Request,datainterface{}){setResponse:=SetResponse{Status:http.StatusText(200),AccessTime:time.Now().Format("02-01-200615:04:05"),Data:data}response,_:=json.Marshal(setResponse)w.Header().Set("Content-Type","Application/json")w.WriteHeader(200)w.Write(响应)}funcHttpResponseError(whttp.ResponseWriter,r*http.Request,datainterface{},codeint){setResponse:=SetResponse{Status:http.StatusText(code),AccessTime:time.Now().Format("02-01-200615:04:05"),Data:data}响应,_:=json.Marshal(setResponse)w.Header().Set("Content-Type","Application/json")w.WriteHeader(code)w.Write(response)}routes.go:packagebookimport("github.com/go-chi/chi""github.com/go-chi/chi/中间件""github.com/graphql-go/handler")funcRegisterRoutes(r*chi.Mux)*chi.Mux{/*GraphQL*/graphQL:=handler.New(&handler.Config{Schema:&Schema,Pretty:true,GraphQL:true,})r.Use(middleware.Logger)r.Handle("/query",graphQL)returnr}最后,创建一个名为main.go的文件main.go:packagemainimport("github.com/go-chi/chi""graphql/book""graphql/infrastructure""log""net/http""net/url")funcmain(){routes:=chi.NewRouter()r:=book.RegisterRoutes(routes)日志.Println("Serverreadyat8080")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()}运行程序的结果如下:创建书目详细信息。GraphQLAdvantages的例子有很多,但是事实证明,GraphQL在文件上传和简单的API方面相比RESTAPI表现不佳。因此,我们首先要了解我们正在构建的系统是否是s适合使用GraphQL作为应用程序设计架构。
