东亚银行、岚图汽车带你解锁 AIGC 时代的数字化人才培养各赛道新模式! 了解详情
写点什么

如何用 Go 语言构建、测试和部署可扩展的 REST API

作者:Oghenevwede Emeni

  • 2022-12-01
    北京
  • 本文字数:5245 字

    阅读完需:约 17 分钟

如何用Go语言构建、测试和部署可扩展的REST API

引言

在本文中,我们将了解如何使用gin框架创建一个简单的 Golang 应用程序。我们还将学习如何使用持续部署工具CircleCI实现自动化测试和部署。

 

Go 是一种静态类型的开源编程语言,由谷歌工程师创建,其唯一的目的是简化复杂的软件开发过程和架构。它的主要特性包括:高性能网络、并发性和易用性。Go 中广泛使用了 Goroutine。Goroutine 是一个在程序中与其他 Goroutine 并行运行的函数。当需要同时做几件事时,Goroutine 会很有用。举例来说,谷歌、Cloudflare、MongoDB、Netflix 和 Uber 几家公司都使用了 Go。

 

Gin是一个基于 Go 的高性能 HTTP Web 框架,可以用来构建微服务和 Web 应用程序。Gin 的主要优势在于,它使得开发人员可以创建高效、可扩展的应用程序,而且不必编写大量的样板代码。它生成的代码简洁、易于阅读。它还内置了路由、用于处理功能的中间件、日志记录器和 Web 服务器。

构建一个简单的 CRUD API

我们将为学院俱乐部的学生管理工具创建一个简单的 API。完成后,俱乐部主席将能够新增学生及检索所有学生。如果想完全按照本教程来操作,则需要做好以下准备:

  1. 安装 Go,了解该语言的基本知识;

  2. 了解测试,知道如何编写测试;

  3. 一个 GitHub 账号;

  4. 一个 CircleCI 账号;

  5. 一个 Heroku 账号。

 

请注意,如果你想使用 Heroku 的免费帐户进行试验,那么 Heroku 很快就会将其停用。不过,这里描述的过程可以很容易地应用于其他大多数云托管平台。

构建一个 CRUD 应用程序

这个简单的学院俱乐部 API 只有两个功能:将学生添加为会员和查看所有会员;没有什么复杂的东西!我们将用到 POST 和 GET 请求。我们不会连接任何数据库,如 MongoDB 或 MySQL。但是,我们将使用本地存储并默认在数据库中创建一个学生。每当服务器重启时,就会自动添加这个学生。

 

让我们开始吧。首先,我们将创建一个项目文件夹,并命名为stup -api。在这个文件夹中,我们将初始化 Golang 程序并安装所需的所有依赖。

mkdir stud-apicd stud-api
复制代码

接下来,我们将初始化go.mod文件,并安装所需的所有依赖:

go mod init stud-apicd stud-apigo get -u github.com/gin-gonic/gin github.com/rs/xid github.com/stretchr/testify 
复制代码

Github.com/rs/xid是一个用于创建惟一标识的库。在这个项目中,我们将用它自动为每个新学生生成一个 ID。我们将用github.com/stretchr/testify包测试各个端点。

 

下面开始讨论 API。简单起见,我们只创建一个名为main.go的文件。这个文件将包含struct 、API 控制器、服务和路由。我们将创建三个端点:

  1. 一个发送欢迎消息的欢迎函数;

  2. 一个将学生添加到数据库的CreateStudent() 函数;

  3. 一个返回数据库中所有已注册学生的GetStudents()函数。

 

下面在新创建的main.go文件中导入三个包:HTTP 包、xID 包和 gin 包。接下来,编写一个main()函数,其中将包含所有的 API 路由。然后,另外创建一个函数WelcomeMessage(),在调用相关的路由时,它会打印一条简单的消息。

package mainimport (	"net/http"	"github.com/gin-gonic/gin"	"github.com/rs/xid")

func main() { //设置路由 router := gin.Default() router.GET("/", WelcomeMessage) router.Run()}//欢迎消息func WelcomeMessage(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"message": "Hey boss!"})}
复制代码

现在,可以使用下面的命令来启动服务器,看看到目前为止我们都做了什么:

go run main.go
复制代码

如果运行成功,则 CLI 将显示“Hey boss!”。这个简单的函数就创建完成了。现在我们将继续讨论数据库和struct

 

我们将构建一个简单的Student struct ,它接受三个参数:学生姓名、学院和年级,并在用户成功添加到数据库时为其生成一个 ID。

//定义学生结构type Student struct {	ID         string `json:"id"`	Name       string `json:"name"`	Department string `json:"department"`	Level      string `json:"level"`}
复制代码

现在,我们创建下本地数据库,它将存储我们传递给服务器的三个值以及生成的 ID。我们将数据库命名为Students,其中会包含一个学生的默认数据,而新创建的任何学生都会添加到这里。

//学生数据库var students = []Student{	{		ID:         "10000xbcd3",		Name:       "Alicia Winds",		Department: "Political Science",		Level:      "Year 3",	},}
复制代码

好了,数据库设计就完成了,现在我们编写下CreateStudent()函数以及与其交互的路由。

//新建一个学生账号func CreateStudent() gin.HandlerFunc {	return func(c *gin.Context) {		var newStudent Student		if err := c.BindJSON(&newStudent); err != nil {			c.JSON(http.StatusBadRequest, gin.H{				"Status":  http.StatusBadRequest,				"Message": "error",				"Data":    map[string]interface{}{"data": err.Error()}})			return		}		//生成一个学生ID		newStudent.ID = xid.New().String()		students = append(students, newStudent)		c.JSON(http.StatusCreated, newStudent)	}

}
复制代码

现在将与该函数交互所需的路由添加到main()函数。

func main() {	-------------	router.POST("/createStudent", CreateStudent())	-------------}
复制代码

要测试到目前为止所做的工作,请启动服务器,并在 Postman 或任何其他环境中测试端点(localhost:8080/createStudent)。在消息体中传递姓名、学院和年级,就会自动生成一个具有惟一 ID 的新用户。请注意,这是一个非持久化数据库。

 

现在,让我们创建最后一个函数。我们将使用它来获取俱乐部数据库中的所有学生。这个请求是一个简单的GET函数,它将搜索学生数据库并返回其中的所有内容。

func GetStudents() gin.HandlerFunc {	return func(c *gin.Context) {		//获取数据库中的所有学生		c.JSON(http.StatusOK, students)	}}
复制代码

最后,我们将创建与新建函数进行交互的路由。我们将把它加入主函数,和其他路由放在一起。

func main() {	------------------	router.GET("/students", GetStudents())	router.Run()}
复制代码

也使用 Postman 测试一下!为此,我们需要启动服务器并访问端点localhost:8080/students。我们所需要做的就是使用 HTTP 谓词GET,不需要包含任何消息体或查询参数。运行成功后,它将返回数据库中的所有学生。这样,这个简单的 CRUD API 就完成了!

编写简单的本地测试

在这一节中,我们将对已创建的端点进行单元测试。目标是确保每个函数的行为都符合预期。为了测试这些函数,我们将使用testify包。此外,我们必须新建一个文件new_test.go。我们将要编写的各种测试都将放在这个文件中。在主目录的根目录中创建完新文件后,我们需要导入几个包。

func main() {	------------------	router.GET("/students", GetStudents())	router.Run()}
复制代码

在 testify 中,执行简单的断言和模拟都很容易。在 Go 中,testing.T对象作为assert函数的第一个参数传入。然后,assert函数会返回一个bool值,说明断言是否成功。testify mock包提供了一种快速创建模拟对象的方法,在编写测试代码时可以用它代替实际的对象。

 

现在,我们将设置一个路由,并为欢迎消息编写一个简单的测试。如下所示,在这个测试中,assert函数将使用变量的相等比较来确定测试参数是否与模拟响应相匹配。

func SetRouter() *gin.Engine {	router := gin.Default()	return router}

func TestWelcomeMessage(t *testing.T) { mockResponse := `{"message":"Hey boss!"}` r := SetRouter() r.GET("/", WelcomeMessage) req, _ := http.NewRequest("GET", "/", nil) w := httptest.NewRecorder() r.ServeHTTP(w, req) responseData, _ := ioutil.ReadAll(w.Body) assert.Equal(t, mockResponse, string(responseData)) assert.Equal(t, http.StatusOK, w.Code)}
复制代码

接下来,我们将使用模拟数据为createStudent()函数编写一个简单的测试。还是使用 xID 包来生成 Student ID,我们会收到一个说明测试是否成功的bool值。

func TestCreateStudent(t *testing.T) {	r := SetRouter()	r.POST("/createStudent", CreateStudent())	studentId := xid.New().String()	student := Student{		ID:         studentId,		Name:       "Greg Winds",		Department: "Political Science",		Level:      "Year 4",}	jsonValue, _ := json.Marshal(student)	req, _ := http.NewRequest("POST", "/createStudent", bytes.NewBuffer(jsonValue))	w := httptest.NewRecorder()	r.ServeHTTP(w, req)	assert.Equal(t, http.StatusCreated, w.Code)}
复制代码

最后,我们将针对GetStudents()函数编写最后一个测试。

func TestGetStudents(t *testing.T) {	r := SetRouter()	r.GET("/students", GetStudents())	req, _ := http.NewRequest("GET", "/students", nil)	w := httptest.NewRecorder()	r.ServeHTTP(w, req)	var students []Student	json.Unmarshal(w.Body.Bytes(), &students)	assert.Equal(t, http.StatusOK, w.Code)	assert.NotEmpty(t, students)}
复制代码

我们已经完成了所有的测试,现在可以在本地运行了。这很简单,只需执行下面这行命令:

GIN_MODE=release go test -v
复制代码

下面是最终结果:



利用持续开发实现测试自动化

CircleCI是一个用于持续集成和交付的平台,可用于 DevOps 实践。在本文中,我们将使用这个 CI/CD 工具实现测试自动化并将代码部署到服务器上。我们先从使用 CircleCI 自动化测试开始说起。

 

确保你有一个 CircleCI 帐户(正如准备工作部分所介绍的那样),并且已经成功地将代码推送到 GitHub。检查 CircleCI 仪表板,确保项目存储库是可见的。

 

现在,在项目目录中,创建文件夹.circleci和配置文件config.yml,该文件将包含自动化测试所需的命令。

配置 config.yaml

该文件包含自动化 Heroku 部署和测试所需的所有配置。我们暂时不关注 Heroku 部分,因为我们更感兴趣的是帮助实现自动化测试的代码。该文件包含检出并运行测试的 Go orb 和作业。在将下面的代码添加到配置文件后,我们需要将其重新推送到 GitHub。

workflows:  heroku_deploy:    jobs:      - build      - heroku/deploy-via-git:            requires:            - build          filters:            branches:              only: mainjobs:  build:    working_directory: ~/repo    docker:      - image: cimg/go:1.17.10    steps:      - checkout      - restore_cache:          keys:            - go-mod-v4-{{ checksum "go.sum" }}      - run:          name: Install Dependencies          command: go get ./...      - save_cache:          key: go-mod-v4-{{ checksum "go.sum" }}          paths:            - "/go/pkg/mod"      - run:          name: Run tests          command: go test -v
复制代码

完成这一步之后,返回 CircleCI 仪表板并选择我们的项目。然后,单击它旁边的 Setup 按钮,并选择我们正在使用的分支。当我们点击 Setup 按钮时,程序将开始运行。构建成功的话应该可以看到如下所示的信息(向下滚动到运行测试的部分)。



就是这样!我们成功地构建了一个简单的 API,创建了本地测试,并实现了测试过程自动化。这个自动化过程意味着,每次向 GitHub 存储库上的分支推送时,管道都会尝试运行测试。

使用 CircleCI 自动部署到 Heroku

首先是配置 Heroku。如果你还没有 Heroku 帐户,就需要创建一个。为了方便部署和自动化,你还需要将 GitHub 配置文件连接到 Heroku 帐户。上述工作完成之后,需要在项目文件夹中创建一个 Procfile(是的,没有扩展名),并向其中添加以下内容:

web: app
复制代码

之后,推送到 GitHub。现在,快速看一下之前创建的config.yaml文件,分析下第一部分。可以看到,我们导入了 Heroku orb,其中还有一个工作流,里面是一个在主存储库中构建和部署代码的作业。

 

回到 Heroku 仪表板,我们必须首先在 Heroku 上创建一个项目,并获取 API 密钥(可以从帐户设置中找)。我们需要把这个密钥添加到我们的 CircleCI 项目。为此,在 CircleCI 上导航到现有项目并选择项目设置。然后转到环境变量部分,添加下面这两个东西:

  1. HEROKU_APP_NAME,值为stud-api (应用程序名称);

  2. HEROKU_API_KEY ,值为我们刚刚从 Heroku 获取的密钥。

 

我们已经成功地配置了我们的 CircleCI 项目,可以向 Heroku 持续部署了。如果没什么问题,在 CircleCI 仪表板上,我们应该可以看到下面这样一条说明构建已经成功的消息:



返回 Heroku 仪表板并检索项目 URL,看看我们都做了什么。这里,URL 是:https://stud-app-api.herokuapp.com/。你可以将想要测试的路由附加到 URL 末尾来测试所有的功能。例如,测试获取所有学生的端点:



小结

持续开发使开发人员能够更快地创建更好的产品。持续集成和开发工具通过自动化操作简化了整个过程,减少了所需的时间或专业知识。CI/CD 工具通过自动化从测试到应用程序快速部署之间的所有事情,帮助我们逐步提高产品质量。

 

原文链接:

https://www.infoq.com/articles/build-deploy-scalable-golang-api/


相关阅读:

REST 如何站到了自己的对立面?

什么是 RESTful,REST api 设计时应该遵守什么样的规则?

2022-12-01 08:008657

评论

发布
暂无评论
发现更多内容

用户增长产品经理招聘帖

梁媛

产品经理

assignment 01

Jove

第一节课总结

Jove

对产品岗位的研究和一些看法

Dylan Zhu

产品经理岗位需求总结

Geek_a32093

Dubbo源码解析-开始篇

冰三郎

Java 分布式 dubbo RPC

产品经理岗位对比

思亭

产品经理调研备忘录(何先生的梦呓)

小蜜蜂

产品经理 能力模型 产品经理训练营 何先生的梦呓 数据能力

Week1:按图索骥—从JD入手做能力建设

Y.

产品 产品经理训练营 极客大学产品经理训练营 求职岗位要求

应聘&招聘

skylar

Job Model

·大蕾同学

在线教育产品经理 & 物流行业的产品经理

哈撒啦岛

产品经理 产品经理训练营

第一章作业:认识产品经理

隋泽

产品经理训练营

行业产品经理岗位分析

Shine

产品

HDFS杂谈:数据读写原理

罗小龙

hadoop hdfs 28天写作

如何给产品团队更好地提出设计反馈

Justin

心理学 产品设计 团队协作 28天写作

ACC是如何实现的(28天写作 Day12/28)

mtfelix

自动驾驶 28天写作

极客大学产品训练营作业(第1周)

朱航

【产品经理训练营】【作业】01

anyou

产品训练营-作业1

简小一

如果公司要招⼀个⾼级版你

向日葵

产品经理训练营

产品经理训练营_Chapter1

芃芃

产品经理训练营

0互联网工作经验的我,面对字节跳动产品岗1723个招聘岗位慌了起来….

Geek_fe4aa7

产品经理训练营 极客大学产品经理训练营

产品经理第一周第二课总结(第一课手贱删除了,回头再补)

克比

极客时间产品训练营-第一周学习总结(上)

Trigger

极客大学产品经理训练营

甲方日常 87

句子

工作 随笔杂谈 日常

产品训练营作业1-李沂秾

克比

HTML(三)——在网页中使用图像img

程序员的时光

程序员 28天写作

第一章学习总结

Kalman

产品经理 产品经理训练营

产品经理岗位对比分析

DwToretto

把我自己做成产品交付给目标岗位

havaguday

如何用Go语言构建、测试和部署可扩展的REST API_语言 & 开发_InfoQ精选文章