欢迎来到爱学习爱分享,在这里,你会找到许多有趣的技术 : )

GitHub 很早就提供 GraphQL API 了,还不学习它就 Out 了

开发者头条 895℃

欢迎各位 Gophers !在本教程中,我们将探索如何使用 Go 和 GraphQL 服务进行交互。在本教程完结之时,我们希望你可以了解到以下内容:

  • GraphQL 的基础知识

  • 使用 Go 构建一个简易的 GraphQL 服务

  • 基于 GraphQL 执行一些基本的查询

在本篇教程中,我们会专注于 GraphQL 在数据检索方面的内容,我们将会使用内存数据源来存储其数据。同时,本篇教程的内容将会为我们之后的教程提供一个良好的基础。

GraphQL 的基础知识

在我们深入探讨之前,我们需要真正地理解 GraphQL 相关的基础知识。换句话说,作为开发人员,我们需要知道,使用它会为我们带来哪些益处。

考虑下,如果有一个系统,它每天需要处理数十万甚至数百万的请求。一般情况下,我们会请求一个面向数据库的 API ,该 API 会返回大量的 JSON 响应体,其中包含了许多冗余信息。

此时,如果我们面对的是一个超大规模的应用程序,那么发送、接收这些冗余信息会产生很多额外的开销。并且会由于负载的关系而阻塞带宽。

事实上,GraphQL 能够减少我们因冗余信息而产生的额外心智负担,并且,它拥有能够描述从服务端返回的数据的能力,这样,我们就可以仅关注当前任务,视图,或其他任何东西所需的数据、内容。

而且,上述的功能仅仅是 GraphQL 能提供给我们的众多益处中的很小的一部分。在接下来的教程中,我们将会介绍更多关于 GraphQL 的益处。

为 API 而生(而不是为数据库而生)的查询语言

一个非常重要的内容就是 GraphQL 不是一个和传统 SQL 一样的查询语言。它是位于 API 之前的一层抽象,且并不依赖于任何特定的数据或存储引擎。

这种设计非常酷,我们可以先建立一个与现有服务交互的 GraphQL 服务,然后围绕这个 GraphQL 进行构建,而无须担心会修改现有的 RESTful API。

REST 和 GraphQL 的区别

首先,让我们看看 RESTful 方法和 GraphQL 方法的区别。想象下我们正在构建一个能返回本站所有教程的服务,如果我们需要某些指定的教程信息,通常来说,我们会创建一个 API 端点以允许我们以一个 ID 来检索指定的教程。

# A dummy endpoint that takes in an ID path parameter
'http://api.tutorialedge.net/tutorial/:id'

如果给定的是一个合法的ID,它将会返回一个响应体,该响应体可能会如下所示:

{
    "title": "Go GraphQL Tutorial",
    "Author": "Elliot Forbes",
    "slug": "/golang/go-graphql-beginners-tutorial/",
    "views": 1,
    "key" : "value"
}

现在,假设我们想创建一个控件,该控件会列出指定的作者撰写的前 5 个帖子。我们可以使用/author/:id这样的 API 来检索出所有由该作者撰写的帖子,然后再执行后续的调用获取排名前 5 的帖子。亦或者,我们可以创建一个新的 API 来返回这些数据。

上述的解决方案听起来并没有什么特别之处,因为它们创建了大量无用的请求或者返回了过多的冗余信息,这也暴露了 RESTful 方法的一些缺陷。

此时,就轮到 GraphQL 入场了。通过 GraphQL ,我们可以在查询中精确定义我们想要返回的数据。因此,如果我们需要上述的教程信息,我们可以创建一个查询,如下所示:

{
	tutorial(id: 1) {
		id
		title
		author {
			name
			tutorials
		}
		comments {
			body
		}
	}
}

随后,它就会返回该教程的作者信息以及指定id教程下该作者所撰写的其他教程列表。这些数据都是我们所需的,但却不用通过发送额外的 REST 请求来获得!多美好,不是吗?

基本设置

目前为止,我们已经了解了 GraphQL 的基础知识以及使用它的益处,下面,让我们看看如何在实战中运用它。

我们将会使用 Go 创建一个简易的 GraphQL 服务,这里,我们是使用graphql-go/graphql[1] 这个库实现的。

设置简易的 GraphQL 服务

使用go mod INIt来初始化我们的项目:

$ Go mod INIt Github.com/elliotforbes/go-graphql-tutorial

接下来,让我们创建一个名为main.go的文件;并从头开始创建一个简易的 GraphQL 服务,它包含一个极简的解析器。

// credit - Go-graphql hello world example
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 run ./...
{"data":{"hello":"world"}}

所以,在一切正常的情况下,我们已经配置完成一个极简的 GraphQL 服务,并创建一个真实的请求发送至该服务。

GraphQL 模式

我们需要分解上述例子中的代码,以便于我们之后的扩展。在fields…代码行开始处,我们定义了一个schema。当我们通过 GraphQL API 执行查询时,我们实质上定义了对象中的哪些字段是我们期望得到的。所以我们必须在schema中定义这些字段。

在Resolve…代码行处,我们定义了一个解析器函数,每当这个特定field被请求时都会触发这个解析器函数。到目前为止,我们仅仅返回了一个”world”字符串,而在此之后,我们会实现查询整个数据库的能力。

查询

让我们继续分解main.go文件的剩下的部分。在query…代码行开始处,我们定义了一个请求hello字段的query。

接着,我们创建了一个params结构体,该结构体包含了对之前定义的schema以及RequestString请求的引用。

最后,我们开始执行请求,并将请求的结果填充到r中。然后我们处理可能出现的错误,并将响应体解析成 JSON ,并将其打印到终端上。

更为复杂的例子

目前为止,我们已经有一个运行中、极简的 GraphQL 服务,我们可以通过它来执行一些查询。我们需要更进一步,构建一个更为复杂的例子。

我们会创建一个 GraphQL 服务,它返回一系列存储于内存中的教程以及相应的作者、评论等信息。

首先,我们需要定义能够表示Tutorial,Author和Comment的结构体 :

type Tutorial struct {
	Title    string
	Author   Author
	Comments []Comment
}

type Author struct {
	Name      string
	Tutorials []int
}

type Comment struct {
	Body string
}

紧接着,我们创建一个极简的populate函数用于返回元素为Tutorial类型的切片。

func populate() []Tutorial {
	author := &Author{Name: "Elliot Forbes", Tutorials: []int{1}}
	tutorial := Tutorial{
		ID:     1,
		Title:  "Go GraphQL Tutorial",
		Author: *author,
		Comments: []Comment{
		Comment{Body: "First Comment"},
	},
}

	var tutorials []Tutorial
	tutorials = append(tutorials, tutorial)

	return tutorials
}

上述的代码将为我们返回一个简单的教程列表,该列表会用于我们在之后进行的解析操作。

创建一个新的对象类型

首先,我们使用graphql.NewObject()在 GraphQL 中创建一个新对象。我们使用 GraphQL 严格的类型来定义三种不同的类型。这些类型将与我们已经定义好的三种结构体相匹配。

Comment结构体无疑是最简单的,它只包含一个字符串类型的字段Body,所以我们能够很容易地将其表示为commentType:

var commentType = graphql.NewObject(
	graphql.ObjectConfig{
	Name: "Comment",
	// we define the name and the fields of our
	// object. In this case, we have one solitary
	// field that is of type string
		Fields: graphql.Fields{
			"body": &graphql.Field{
				Type: graphql.String,
			},
		},
	},
)

接着,我们需要处理Author结构体,并将其定义为新的graphql.NewObject(),这会稍微复杂一点,因为该结构体既包含String字段,也包含一个Int值列表,这些值表示该作者所编写的教程 ID 列表。

var authorType = graphql.NewObject(
	graphql.ObjectConfig{
		Name: "Author",
		Fields: graphql.Fields{
			"Name": &graphql.Field{
				Type: graphql.String,
			},
			"Tutorials": &graphql.Field{
		// we'll use NewList to deal with an array
		// of int values
				Type: graphql.NewList(graphql.Int),
			},
		},
	},
)

最后,我们定义了tutorialType,它会封装一个author,一个元素为comment的数组,ID以及title。

var tutorialType = graphql.NewObject(
	graphql.ObjectConfig{
		Name: "Tutorial",
		Fields: graphql.Fields{
			"id": &graphql.Field{
				Type: graphql.Int,
			},
			"title": &graphql.Field{
				Type: graphql.String,
			},
			"author": &graphql.Field{
		// here, we specify type as authorType
		// which we've already defined.
		// This is how we handle nested objects
				Type: authorType,
			},
			"comments": &graphql.Field{
				Type: graphql.NewList(commentType),
			},
		},
	},
)

更新模式

到目前为止,我们已经定义了一个完整的Type系统,接下来,我们需要更新Schema以映射到这些类型上。我们会定义两个不同的Field,第一个是我们的tutorial字段,该字段允许我们根据传入的 ID 参数检索单个tutorial。第二个字段则是一个list,它允许我们检索存储于内存中的完整tutorials列表。

// Schema
	fields := graphql.Fields{
		"tutorial": &graphql.Field{
			Type: tutorialType,
			// it's Good form to add a description
			// to each field.
			Description: "Get Tutorial By ID",
			// We can define arguments that allow us to
			// pick specific tutorials. In this case
			// we want to be able to specify the ID of the
			// tutorial we want to retrieve
			Args: graphql.FieldConfigArgument{
				"id": &graphql.ArgumentConfig{
					Type: graphql.Int,
				},
			},
			Resolve: func(p graphql.ResolveParams) (interface{}, error) {
				// take in the ID argument
				id, ok := p.Args["id"].(int)
				if ok {
					// Parse our tutorial array for the matching id
					for _, tutorial := range tutorials {
						if int(tutorial.ID) == id {
							// return our tutorial
							return tutorial, nil
						}
					}
				}
				return nil, nil
			},
		},
		// this is our `list` endpoint which will return all
		// tutorials available
		"list": &graphql.Field{
			Type:        graphql.NewList(tutorialType),
			Description: "Get Tutorial List",
			Resolve: func(params graphql.ResolveParams) (interface{}, error) {
				return tutorials, nil
			},
		},
	}

到目前为止,我们创建了Types并更新了 GraphQL 模式,看起来我们似乎做的还算不错。

测试它是否能够工作

让我们先尝试新的 GraphQL 服务,使用我们最新提交的查询。通过更改main函数中的query来尝试list模式。

// Query
query := `
	{
		list {
			id
			title
			comments {
				body
			}
			author {
				Name
				Tutorials
			}
		}
	}
`

我们需要分析一下,在此查询中有一个特殊的root对象。此时,我们描述了我们所期望的对象的list字段。在按照list模式返回的结果列表中,我们希望能够看到id,title,comments和author。

当我们运行这个查询后,我们将会看到如下输出:

$ go run ./...
{"data":{"list":[{"author":{"Name":"Elliot Forbes","Tutorials":[1]},"comments":[{"body":"First Comment"}],"id":1,"title":"Go GraphQL Tutorial"}]}}

正如我们所看到的,我们的查询以 JSON 的格式返回了所有教程列表,这看起来和我们定义的初始查询非常相似。

现在让我们通过tutorial模式来执行另一个查询 :

query := `
	{
		tutorial(id:1) {
			title
			author {
			Name
			Tutorials
			}
		}
	}
`

当我们再一次运行它,我们会看到它成功地检索到了内存中唯一一个ID=1的教程。

$ go run ./...
{"data":{"tutorial":{"author":{"Name":"Elliot Forbes","Tutorials":[1]},"title":"Go GraphQL Tutorial"}}}

完美,从输出结果上看,我们的list和tutorial模式能够正常工作。

挑战:尝试在populate函数中更新教程列表,使其可以返回更多的教程。一旦我们完成了这一步,我们就可以尝试使用查询,并加深对查询的理解。

总结

注意:本教程全部的源代码位于这里:main.go[2]

这就是我们在本次初始教程中所介绍的所有内容。我们成功地配置了一个由内存数据存储支持的极简的 GraphQL 服务。

在下篇教程中,我们将查看 GraphQL 变更的概念,并改造我们的数据源以使用 NoSQL 数据库。关于下一篇教程可以阅读Go GraphQL Beginners Tutorial – Part2[3]


via: https://tutorialedge.net/golang/go-graphql-beginners-tutorial/

作者:Elliot Forbes[4]译者:barryz[5]校对:magichan[6]

本文由 GCTT[7] 原创编译,Go 中文网[8] 荣誉推出

参考资料

[1]

graphql-go/graphql: https://github.com/graphql-go/graphql

[2]

main.go: https://gist.github.com/elliotforbes/9b8400ef5154eb3420e409aeffe39633

[3]

Go GraphQL Beginners Tutorial – Part2: https://tutorialedge.net/golang/go-graphql-beginners-tutorial-part-2/

[4]

Elliot Forbes: https://twitter.com/elliot_f

[5]

barryz: https://github.com/barryz

[6]

magichan: https://github.com/magichan

[7]

GCTT: https://github.com/studygolang/GCTT

[8]

Go 中文网: https://studygolang.com/

转载请注明:爱学习爱分享 » GitHub 很早就提供 GraphQL API 了,还不学习它就 Out 了

喜欢 (0)or分享 (0)