百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

迷你指南——结合 MySQL 构建一个基于 Go 的 REST API 微服务

toyiye 2024-06-21 11:59 11 浏览 0 评论

点击上方蓝色“Go语言中文网”关注我们,领全套Go资料,每天学习 Go 语言

我最近发现我在 Storytel 公司的日常工作和我自己的小项目 Wiseer[1] 中已经编写和部署了很多基于 Go 的微服务。在本篇迷你指导中,我会结合 MySQL 数据库创建一个简单的 REST-API。完整项目的代码会在文章的最后给出。

如果你还不熟悉 Go 语言,那么我推荐 这篇 Go 指南[2] 作为本篇文章的补充。

让我们开始吧!

准备 API

我们在着手时需要做的第一件事是选择一个用于路由的库。路由就是将一个 URL 与一个可执行的函数连接在一起。我觉得 mux 库[3] 在路由功能上表现得很好,当然还有其他可选的库如 httprouter[4] 和 pat[5]在性能上也差不多。在本文中我将会使用 mux。

简单起见,我们将会创建一个用于打印一条信息的端点

package main
import (
 "log"
 "net/http"
 "github.com/gorilla/mux"
)
func setupRouter(router *mux.Router) {
 router.
  Methods("POST").
  Path("/endpoint").
  HandlerFunc(postFunction)
}
func postFunction(w http.ResponseWriter, r *http.Request) {
 log.Println("You called a thing!")
}
func main() {
 router := mux.NewRouter().StrictSlash(true)
 setupRouter(router)
 log.Fatal(http.ListenAndServe(":8080", router))
}

上面的代码创建了一个路由,将一个 URL 与一个处理函数(代码中是 postFunction)连接在一起,然后启动了一个服务,并将 8080 端口给这个路由使用。

很简单,嗯哼?

连接数据库

让我们把上面的代码和 MySQL 数据库连接起来。Go 为 SQL 数据库提供了一个接口,但它还需要一个驱动。在这个例子中我使用 go-sql-driver[6] 作为驱动。

package db
import (
 "database/sql"
 _ "github.com/go-sql-driver/mysql"
)
func CreateDatabase() (*sql.DB, error) {
 serverName := "localhost:3306"
 user := "myuser"
 password := "pw"
 dbName := "demo"
 connectionString := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4&collation=utf8mb4_unicode_ci&parseTime=true&multiStatements=true", user, password, serverName, dbName)
 db, err := sql.Open("mysql", connectionString)
 if err != nil {
  return nil, err
 }
 return db, nil
}

上面的代码被放在另一个叫做 db 的包中,并且假设有一个运行在 localhost:3306 的名字叫 demo 数据库。返回的数据库变量自动持有这个数据库的连接池。

让我们更新一下上一个代码片段的 postFunction 来使用数据库。

func postFunction(w http.ResponseWriter, r *http.Request) {
 database, err := db.CreateDatabase()
 if err != nil {
  log.Fatal("Database connection failed")
 }
 _, err = database.Exec("INSERT INTO `test` (name) VALUES ('myname')")
 if err != nil {
  log.Fatal("Database INSERT failed")
 }
 log.Println("You called a thing!")
}

就是这样!它相当简单,当然上面的代码还有一些问题以及功能缺失。这的确有些棘手,但不要放弃!??

结构体和依赖

如果你检查了上面的代码,你可能已经注意到了每次 API 调用时都会打开一个数据库连接,虽然已经被打开的数据库 对并发使用是安全的[7] 。我们需要一些依赖管理手段来确保我们只打开一次数据库,为此,我们将要使用结构体。

package app
import (
 "database/sql"
 "log"
 "net/http"
 "github.com/gorilla/mux"
)
type App struct {
 Router   *mux.Router
 Database *sql.DB
}
func (app *App) SetupRouter() {
 app.Router.
  Methods("POST").
  Path("/endpoint").
  HandlerFunc(app.postFunction)
}
func (app *App) postFunction(w http.ResponseWriter, r *http.Request) {
 _, err := app.Database.Exec("INSERT INTO `test` (name) VALUES ('myname')")
 if err != nil {
  log.Fatal("Database INSERT failed")
 }
 log.Println("You called a thing!")
 w.WriteHeader(http.StatusOK)
}

我们先创建一个叫做 app 的新的包来存放我们的结构体和它的 方法[8]。我们的 App 结构体有两个字段;一个是在第 17 行被调用的 Router,另一个是在第 24 行被调用的 Database。我们同时在第 30 行方法结束的时候手动设置了返回状态码。

main 包以及其中的方法也需要一点小改变来使用新的 App 结构体。我们从 main 包中移除 postFunction 方法和 setupRouter 方法,因为这俩方法已经在 app 包中了。我们留下这些:

package main
import (
 "log"
 "net/http"
 "github.com/gorilla/mux"
 "github.com/johan-lejdung/go-microservice-api-guide/rest-api/app"
 "github.com/johan-lejdung/go-microservice-api-guide/rest-api/db"
)
func main() {
 database, err := db.CreateDatabase()
 if err != nil {
  log.Fatal("Database connection failed: %s", err.Error())
 }
 app := &app.App{
  Router:   mux.NewRouter().StrictSlash(true),
  Database: database,
 }
 app.SetupRouter()
 log.Fatal(http.ListenAndServe(":8080", app.Router))
}

为了使用我们的新结构体,我们打开了一个数据库连接并创建了一个新路由。然后我们把他们存放到我们新的 App 结构体对应字段中。

恭喜!现在你已经有了一个数据库连接了,可以对即将进入的 API 请求并发调用了。

在最后一步中,我们会在路由中添加一个返回 JSON 数据的 GET 方法。我们从添加一个用于填充我们的数据的结构体开始,并且把这些字段映射为 JSON。

package app
import (
 "time"
)
type DbData struct {
 ID   int       `json:"id"`
 Date time.Time `json:"date"`
 Name string    `json:"name"`
}

接着,我们在 app.go 文件中添加一个用于处理请求并且把数据写回客户端响应的新方法 getFunction。这个文件最后看起来是这个样子的。

package app
import (
 "database/sql"
 "encoding/json"
 "log"
 "net/http"
 "github.com/gorilla/mux"
)
type App struct {
 Router   *mux.Router
 Database *sql.DB
}
func (app *App) SetupRouter() {
 app.Router.
  Methods("GET").
  Path("/endpoint/{id}").
  HandlerFunc(app.getFunction)
 app.Router.
  Methods("POST").
  Path("/endpoint").
  HandlerFunc(app.postFunction)
}
func (app *App) getFunction(w http.ResponseWriter, r *http.Request) {
 vars := mux.Vars(r)
 id, ok := vars["id"]
 if !ok {
  log.Fatal("No ID in the path")
 }
 dbdata := &DbData{}
 err := app.Database.QueryRow("SELECT id, date, name FROM `test` WHERE id = ?", id).Scan(&dbdata.ID, &dbdata.Date, &dbdata.Name)
 if err != nil {
  log.Fatal("Database SELECT failed")
 }
 log.Println("You fetched a thing!")
 w.WriteHeader(http.StatusOK)
 if err := json.NewEncoder(w).Encode(dbdata); err != nil {
  panic(err)
 }
}
func (app *App) postFunction(w http.ResponseWriter, r *http.Request) {
 _, err := app.Database.Exec("INSERT INTO `test` (name) VALUES ('myname')")
 if err != nil {
  log.Fatal("Database INSERT failed")
 }
 log.Println("You called a thing!")
 w.WriteHeader(http.StatusOK)
}

数据库迁移

我们来为项目添加最后一个功能。当数据库与一个应用或者服务耦合过深时,会造成令人头疼的问题,可以通过适当的处理数据库迁移来解决这个问题。我们会使用 migrate 库[9] 来做这件事情,然后扩展我们的 db 包。

就是下面这些相当长的嵌入的代码片段。

package db
import (
 "database/sql"
 "fmt"
 "log"
 "os"
 _ "github.com/go-sql-driver/mysql"
 "github.com/golang-migrate/migrate"
 "github.com/golang-migrate/migrate/database/mysql"
 _ "github.com/golang-migrate/migrate/source/file"
)
func CreateDatabase() (*sql.DB, error) {
 // I shortened the code here. Here is where the DB setup were made.
 // In order to save some space I've removed the connection setup, but it can
 // be seen here: https://gist.github.com/johan-lejdung/ecea9dab9b9621d0ceb054cec70ae676#file-database_connect-go
 if err := migrateDatabase(db); err != nil {
  return db, err
 }
 return db, nil
}
func migrateDatabase(db *sql.DB) error {
 driver, err := mysql.WithInstance(db, &mysql.Config{})
 if err != nil {
  return err
 }
 dir, err := os.Getwd()
 if err != nil {
  log.Fatal(err)
 }
 migration, err := migrate.NewWithDatabaseInstance(
  fmt.Sprintf("file://%s/db/migrations", dir),
  "mysql",
  driver,
 )
 if err != nil {
  return err
 }
 migration.Log = &MigrationLogger{}
 migration.Log.Printf("Applying database migrations")
 err = migration.Up()
 if err != nil && err != migrate.ErrNoChange {
  return err
 }
 version, _, err := migration.Version()
 if err != nil {
  return err
 }
 migration.Log.Printf("Active database version: %d", version)
 return nil
}

数据库连接打开后,我们添加的 migrateDatabase 函数会被调用来开始迁移过程。

我们也会添加一个 MigrationLogger 结构体来处理迁移过程中的日志,代码可以在 这里[10] 被看到,而且这个结构体在第 45 行被使用。

迁移是通过普通的 sql 语句实现的。迁移文件从第 37 行显示的文件夹中被读取。

每当数据库被打开后,所有未被应用的数据库迁移将会被应用。这样将会使数据库在不需要人为干预的情况下保持最新。

在 docker-compose 文件中保存了数据库使得多机开发变得很简单。

打包

终于走到这一步了

一个不能部署的微服务是没有用的,因此我们加一个 Dockerfile 来打包这个应用以便于能够很容易的进行的分发——然后本文就到此结束了。

FROM golang:1.11 as builder
WORKDIR $GOPATH/src/github.com/johan-lejdung/go-microservice-api-guide/rest-api
COPY ./ .
RUN GOOS=linux GOARCH=386 Go build -ldflags="-w -s" -v
RUN cp rest-api /
FROM alpine:latest
COPY --from=builder /rest-api /
CMD ["/rest-api"]

构造好的镜像仅仅 10 MB

下面是代码。

go-microservice-api-guide


我希望你能觉得这很有趣并且能从中学到些东西!

当然这个项目还有很多地方可以进行完善,但这个任务就交给你和你的创造力了

如果你喜欢这篇文章,把它分享给你的朋友,当然在 Twitter 上分享是最好的!

我计划在尽可能短的文章中涉及到更多的进阶话题。目前我想到的话题有:中间件的使用,测试,依赖注入和服务层。


via: https://dev.to/johanlejdung/a-mini-guide-build-a-rest-api-as-a-go-microservice-together-with-mysql-27m2

作者:Johan Lejdung

译者:Ollyder

校对:JYSDeveloper

本文由 GCTT 原创编译,Go 中文网荣誉推出

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码