最新发布《数智时代的AI人才粮仓模型解读白皮书(2024版)》,立即领取! 了解详情
写点什么

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

评论

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

我们为什么要学习数学建模?

图灵教育

数学 数学建模

购买小间距LED显示屏的三个建议

Dylan

LED显示屏

C++|TCP客户端中发送文件

中国好公民st

c++ TCP通信 7月月更

激动人心!2022开放原子全球开源峰会报名火热开启!

kk-OSC

开源 开发原子全球开源峰会 开源峰会

手把手带你入门Apache伪静态的配置

迷彩

Apache SEO伪静态 7月月更

什么叫做信息安全?包含哪些内容?与网络安全有什么区别?

行云管家

网络安全 信息安全 数据安全

IPv6与IPv4的区别 网信办等三部推进IPv6规模部署

郑州埃文科技

ipv6 ipv4 IP地址

【Python技能树共建】python urllib 模块

梦想橡皮擦

Python 7月月更

微帧科技荣获全球云计算大会“云鼎奖”!

微帧Visionular

webRTC SDP mslabel lable

Boll

WebRTC

7 大主题、9 位技术大咖!龙蜥大讲堂7月硬核直播预告抢先看,今天见

OpenAnolis小助手

云原生 技术干货 龙蜥大讲堂 7 月预告 精美周边

百问百答第45期:应用性能探针监测原理-node JS 探针

博睿数据

自动化 博睿数据 性能检测 百问百答 智能运维AIOps

TDengine 社区问题双周精选 | 第三期

TDengine

数据库 tdengine 时序数据库

ACID事务理论

源字节1号

软件开发

《信息系统项目管理师》备考笔记---信息化知识

IT蜗壳-Tango

软考 7月月更 信息系统项目管理师

Spring Cloud源码分析之Eureka篇第二章:注册中心启动类上的注解EnableEurekaServer

程序员欣宸

Java spring SpringCloud 7月月更

Python|数据结构——列表和元组

AXYZdong

7月月更

spark调优(一):从hql转向代码

怀瑾握瑜的嘉与嘉

spark 7月月更

分类TAB商品流多目标排序模型的演进

得物技术

算法 得物

NFT 交易市场主要使用 ETH 本位进行交易的局面是如何形成的?

NFT Research

区块链 NFT

龙蜥社区第九次运营委员会会议顺利召开

OpenAnolis小助手

阿里云 开源 龙蜥社区 运营委员会 运营代表

leetcode 10. Regular Expression Matching 正则表达式匹配 (困难)

okokabcd

LeetCode 动态规划 数据结构与算法

大疆车载从多家数据库中选定 TDengine 存储海量数据

TDengine

数据库 tdengine 物联网 时序数据库

CODING DevSecOps 助力金融企业跑出数字加速度

CODING DevOps

研发效能 DevSecOps CODING 代码评审 持续安全交付

居家办公那些事|社区征文

CrazyGeek1996

居家办公 社区征文 7月月更

超高效!Swagger-Yapi的秘密

百度Geek说

后端 swagger

直播预告|如何借助自动化工具落地DevOps(文末福利)

云智慧AIOps社区

DevOps 云原生 运维开发 自动化构建工具

【云资源】云资源安全管理用什么软件好?为什么?

行云管家

云计算 安全管理 云资源

程序员内卷和保持行业竞争力

沃德

程序员 7月月更

OpenHarmony应用开发之Navigation组件详解

坚果

HarmonyOS OpenHarmony 7月月更

《2022年中国银行业RPA供应商实力矩阵分析》研究报告正式启动

易观分析

银行用户

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