社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  DATABASE

结合 MySQL 构建一个基于 Go 的 REST API 微服务

Go语言中文网 • 3 年前 • 361 次点击  
点击上方蓝色“Go语言中文网”关注,每天一起学 Go

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

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

让我们开始吧!

本文是 Go语言中文网组织的 GCTT 翻译,发布在 Go语言中文网公众号,转载请联系我们授权。

准备 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[11]


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

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

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

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


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

作者:Johan Lejdung[12]译者:Ollyder[13]校对:JYSDeveloper[14]

本文由 GCTT[15] 原创编译,Go 中文网[16] 荣誉推出,发布在 Go语言中文网公众号,转载请联系我们授权。

参考资料

[1]

Wiseer: https://wiseer.io/

[2]

这篇 Go 指南: https://wiseer.io/

[3]

mux 库: https://github.com/gorilla/mux

[4]

httprouter: https://github.com/gorilla/mux

[5]

pat: https://github.com/bmizerany/pat

[6]

go-sql-driver: https://medium.com/r/?url=https%3A%2F%2Fgithub.com%2Fgo-sql-driver%2Fmysql

[7]

对并发使用是安全的: https://golang.org/pkg/database/sql/#Open

[8]

方法: https://gobyexample.com/methods

[9]

migrate 库: https://github.com/golang-migrate/migrate

[10]

这里: https://github.com/johan-lejdung/go-microservice-api-guide/blob/master/rest-api/db/migrationlogger.go

[11]

go-microservice-api-guide: https://github.com/johan-lejdung/go-microservice-api-guide

[12]

Johan Lejdung: http://github.com/johan-lejdung

[13]

Ollyder: https://github.com/Ollyder

[14]

JYSDeveloper: https://github.com/JYSDeveloper

[15]

GCTT: https://github.com/studygolang/GCTT

[16]

Go 中文网: https://studygolang.com/



推荐阅读


福利

我为大家整理了一份从入门到进阶的Go学习资料礼包,包含学习建议:入门看什么,进阶看什么。关注公众号 「polarisxu」,回复 ebook 获取;还可以回复「进群」,和数万 Gopher 交流学习。

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/118994
 
361 次点击