写点什么

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

评论

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

Spring Cloud 2020.0.0正式发布,再见了Netflix

YourBatman

Spring Cloud Spring Boot netflix 2020.0.0

最有技术含量的面试

escray

面试 面经 七日更 十日谈

揭开阿里巴巴复杂任务资源混合调度技术面纱

阿里巴巴云原生

云计算 阿里云 性能优化 云原生 资源调度

揭秘大流量场景下发布如丝般顺滑背后的原因

阿里巴巴中间件

阿里巴巴

附PPT丨AWS基于数据湖构建云上的数据分析架构

dbaplus社群

数据湖 AWS

手写线程池,对照学习ThreadPoolExecutor线程池实现原理!

小傅哥

Java 小傅哥 线程池 七日更 ThreadPoolExecutor

30G 上亿数据的超大文件,如何快速导入生产环境?

楼下小黑哥

Java MySQL 并发编程 线程池

引领云原生发展浪潮 阿里云开启云原生大规模落地元年

阿里巴巴云原生

云计算 阿里巴巴 阿里云 最佳实践 云原生

Android uni-app实现音视频通话

anyRTC开发者

uni-app android 音视频 WebRTC 跨平台

单点破局思维|技术人应知的创新思维模型(8)

Alan

个人成长 技术人应知的创新思维模型 七日更 28天写作

代码零改动Serverless架构升级?这家在线编程教育企业是这么做的

阿里巴巴中间件

Python Serverless

Shell简介

入门小站

Shell

Go中的Channel背后的设计哲学

soolaugust

Go Concurrency Patterns 七日更 CSP Go 语言

如何破解AI数据困境?京东智联云联邦学习平台有良方

京东科技开发者

人工智能 大数据 学习

现代JavaScript:ES6+ 中的 Imports,Exports,Let,Const 和 Promise

葡萄城技术团队

Java ES6

TypeScript | 第三章:函数、泛型和枚举

梁龙先森

typescript 编程 大前端 七日更

Serverless 落地之痛怎么解?

阿里巴巴中间件

Serverless

MySQL字符集修改实战教程

Simon

MySQL 字符集 七日更

世界之书:《禅与摩托车维修艺术》与发现良质

lidaobing

禅与摩托车维修艺术 28天写作

XRP瑞波币系统软件开发|XRP瑞波币APP开发

系统开发

语音助手中的复杂语义表达方法

DataFunTalk

AI nlp

12张图带你彻底理解分布式事务!!

冰河

分布式事务 BASE理论 TCC ACID CAP理论

《数据分析》PDF免费下载

计算机与AI

数据分析

夜莺二次开发指南-监控系统(1)

ning

滴滴夜莺 夜莺监控

任务发布系统软件开发|任务发布APP开发

系统开发

如何在 20 分钟内给你的 K8s PaaS 上线一个新功能?

阿里巴巴云原生

阿里云 容器 运维 云原生

wildfly 21的domain配置

程序那些事

程序那些事 wildfly wildfly21 配置管理 domain模式

JDK 16 即将发布,新特性速览!

xcbeyond

Java 七日更

生产环境全链路压测建设历程 18:某快递 A 股上市公司的生产压测案例之中篇

数列科技杨德华

全链路压测 七日更

夜莺二次开发指南-监控系统(2)

ning

滴滴夜莺 夜莺监控

打开数“智”化之门,一字之差带来的思考

京东科技开发者

DevOps IoT 新基建 智能

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