写点什么

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

评论

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

安畅SmartOps混合云平台架构的演进之道

安畅Anchnet

云原生

iOSApp上架流程

雪奈椰子

iOS上架 ios打包

IoT物联网设备「固件升级」OTA,「资源包更新」最佳实践——实践类

阿里云AIoT

运维 物联网 数据格式

2023 年openEuler 社区技术委员会增选,新增2位委员

openEuler

Linux 开源 操作系统 openEuler 资讯

基于云基础设施快速部署 RocketMQ 5.0 集群

Apache RocketMQ

RocketMQ 云原生 消息队列

DTALK直播预约 | 金融行业嘉宾分享:金融机构数据治理实践路径

袋鼠云数栈

IM通讯协议专题学习(七):手把手教你如何在NodeJS中从零使用Protobuf

JackJiang

即时通讯 protobuf im开发

Apache Tomcat 存在 JsonErrorReportValve 注入漏洞(CVE-2022-45143)

墨菲安全

安全漏洞 CVE

直播 | StarRocks 联合腾讯云分享 EMR-StarRocks 的降本增效之路

StarRocks

数据库

Ios app如何上架​

雪奈椰子

iOS上架 ios打包

累计装机超过300万套 欧拉操作系统跨越生态拐点

Geek_2d6073

IoT物联网平台「设备影子」开发实战——实践类

阿里云AIoT

JavaScript json 物联网 API 储存

2022 Apache APISIX 年度记忆

API7.ai 技术团队

api 网关 APISIX 年终盘点 apache 社区

为iframe正名,你可能并不需要微前端

阿里巴巴终端技术

前端 微前端 iframe

强强联手 | 尚硅谷&腾讯云EMR离线数仓教程发布

小谷哥

基于开源体系的云原生微服务治理实践与探索

阿里巴巴云原生

阿里云 开源 云原生 service mesh

金融数字化为何需要低代码开发平台

力软低代码开发平台

为什么说 数据服务是数据中台的标配?

用友BIP

一文了解华为FusionInsight MRS HBase的集群隔离方案RSGroup

华为云开发者联盟

大数据 后端 华为云 企业号 1 月 PK 榜

划重点,2023 年最值得关注的 8 大技术趋势

PreMaint

人工智能 云计算 5G 技术趋势 元宇宙

2022 最新版 JDK 17 下载与安装 步骤演示 (图示版)

雪奈椰子

iOS上架 ios打包

站酷基于服务网格ASM的生产实践

阿里巴巴云原生

阿里云 云原生 服务网格

Ios海外上架

雪奈椰子

ios打包

高并发环境下构建缓存服务,你需要注意这6点

华为云开发者联盟

高并发 开发 华为云 企业号 1 月 PK 榜

YMatrix v5.0 发布:138 项深度优化,重塑集群架构,实现性能及高可用性全方位提升!

YMatrix 超融合数据库

OLAP OLTP 超融合数据库 数据库架构设计 YMatrix

重磅发布丨从云原生到Serverless,先行一步看见更大的技术想象力

阿里巴巴云原生

阿里云 Serverless 云原生

云渲染市场安全吗?如何保证数据安全、财产安全?

Renderbus瑞云渲染农场

云渲染 云渲染农场 云渲染安全

EMQ携“云边协同IIoT解决方案”亮相2022世界工业互联网产业大会

EMQ映云科技

人工智能 物联网 IoT 云边协同 企业号 1 月 PK 榜

2023年AI十大展望:GPT-4领衔大模型变革,谷歌拉响警报,训练数据告急

OneFlow

人工智能 深度学习

Iosapp上架流程

雪奈椰子

iOS上架 ios打包

Apache RocketMQ 斩获 InfoQ 2022 年度十大开源新锐项目

阿里巴巴云原生

阿里云 云原生 Apache RocketMQ

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