写点什么

如何用 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:009887

评论

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

基线提升至96.45%:2022 司法杯犯罪事实实体识别+数据蒸馏+主动学习

汀丶人工智能

知识蒸馏 命名实体识别 11月月更

BI分析助力企业数字营销与运营,瓴羊Quick BI让企业决策更精准

巷子

Koordinator 1.0 正式发布:业界首个生产可用、面向规模场景的开源混部系统

云布道师

云原生 云栖大会

web3.0区块链挖矿游戏dapp系统开发合约定制(成熟技术)

开发微hkkf5566

BSC/BNB链质押挖矿dapp系统开发(案例演示)

开发微hkkf5566

【电商实战01】如何快速编写api层和model层?

王中阳Go

golang 高效工作 学习方法 11月月更 电商实战

高并发系统谨防被一行日志击垮

小小怪下士

Java 程序员 高并发

Webpack构建速度优化

Geek_02d948

webpack

Java Web(二)MyBatis

浅辄

Java web mybaits 11月月更

一个 MySQL 隐式转换的坑,差点把服务器整崩溃了

Java全栈架构师

Java MySQL 数据库 程序员 后端

说说Nodejs高并发的原理

coder2028

node.js

解密负载均衡技术和负载均衡算法

京东科技开发者

负载均衡 算法 负载均衡算法 注册表 负载均衡技术

合约广告平台架构演进实践

百度Geek说

业务架构 企业号十月 PK 榜 广告B端系统

java培训学习机构怎么选择

小谷哥

webpack实战,手写loader和plugin

Geek_02d948

webpack

2022VDC游戏生态专场:打造更健康长久的游戏生态

Geek_2d6073

HDC2022 HarmonyOS学生公开课第二届成功举办,年轻创新力量生生不息!

科技汇

Node.js实现大文件断点续传

coder2028

node.js

web前端培训班学习前景怎么样

小谷哥

web3.0多链挖矿dapp系统开发技术详情

开发微hkkf5566

Webpack配置实战

Geek_02d948

webpack

2022VDC云与基础架构专场:筑牢云原生与基础架构发展基石 多维助力效能提升

Geek_2d6073

自学前端没有找到工作,怎么做呢

小谷哥

当大火的文图生成模型遇见知识图谱,AI画像趋近于真实世界

阿里云大数据AI技术

深度学习 PyTorch 知识图谱 企业号十月 PK 榜 图文生成

推动中小企业数字化转型,终于“有据可依”了

雨果

低代码 数字化转型

高频数采、实时流计算:EMQ储能可预测维护系统方案|智慧工厂系列专题06

EMQ映云科技

物联网 IoT emqx 11月月更 云边协同

JS词法环境和执行上下文

hellocoder2029

JavaScript

JS知识点梳理之作用域、作用域链、柯里化、闭包

hellocoder2029

JavaScript

李珣同款爱心特效代码,加DIY教程,快拿去送给你喜欢的那个ta吧。

bug菌

跳动的爱心 代码爱心 爱心代码

Pipy:保护 Kubernetes 上的应用程序免受 SQL 注入和 XSS 攻击

Flomesh

程序员 微服务 服务网格 Pipy

说说Nodejs高并发的原理

coder2028

node.js

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