vlambda博客
学习文章列表

使用Gin,MySQL和Docker开发博客(Part 1)



本文侧重的读者

这部分内容重点不在Go基础。侧重于如何构建后端服务。内容特别适合热衷于使用Gin框架来做web开发的读者。

学习目标

这个系列介绍如何使用Go来创建REST APIs基础。完成这个系列后你就学会了以下内容:

  • 基于MySQL和Docker配置本地Go开发环境

  • Gin框架实现CRUD接口

  • DDD领域驱动开发

  • Go依赖注入

  • 使用Firebase认证等

要求

  • 安装Go(go1.16.3版本 linux/amd64)

  • Docker (版本 20.10.6)和Docker-compose(版本1.29.1)

  • 最好是Linux开发环境

  • 理解Restful服务/API

以上提到的工具已经在我的机器上安装。你可以在这个链接中找到本系列内容的所有源代码。

开始

首先在你的机器上面创建blog目录。然后在blog目录下使用如下命令初始化Go module。

go mod init blog

上面的命令创建了一个go.mod文件。它将允许您管理项目依赖包。让我们添加一些依赖项。

go get github.com/gin-gonic/gin

上面的命令将安装Gin框架和Gorm并创建go.sum文件。该文件存储有关依赖项及其版本信息。
Gin是一个轻量级的、文档丰富的、快速的HTTP web框架。创建者声称,Gin的速度比其他类似的框架快40倍。

启动Hello world服务器

在项目根路径创建一个main.go文件;打开您喜欢的编辑器并编写以下代码:

package main

import (
"net/http"
"github.com/gin-gonic/gin"
)

func main() {
router := gin.Default() //new gin router initialization
router.GET("/", func(context *gin.Context) {
context.JSON(http.StatusOK, gin.H{"data": "Hello World !"})
}) // first endpoint returns Hello World
router.Run(":8000") //running application, Default port is 8080
}

我们梳理下main.go中的代码:

  • package main:每个go文件都是一个包。main包是一个项目的入口。

  • import:导入项目的依赖

  • func main:当运行项目时,首先调main函数。在router上注册了一个/路径。配置一个路由或web接口需要做两件事:

    1、Endpoint:它是获取数据的路径。例如,如果访问者想获得所有的帖子,他们将获取/posts这个端点。

    2、Handler:它决定如何向endpoint提供数据,是业务逻辑,比如从数据库获取或保存数据,验证用户输入等等。上下文对象的JSON方法用于发送JSON格式的数据。此方法以HTTP状态代码和JSON响应作为参数。
    使用如下命令运行项目:

go run main.go

如果您看到类似于下面的输出,这意味着您的web服务已经工作了。

[GIN-debug] GET    /      --> main.main.func1 (3 handlers)
[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on :8000

在浏览器上访问http://localhost:8000/。你应该看到{"data":"Hello World !"}。

配置docker

上述运行服务的方法是Go运行应用程序常用方式。让我们通过Docker的方式来运行go项目。在项目的根目录下创建一个Dockerfile文件,并添如下代码:

FROM golang:alpine

RUN mkdir /app

WORKDIR /app

ADD go.mod .
ADD go.sum .

RUN go mod download
ADD . .

RUN go get github.com/githubnemo/CompileDaemon

EXPOSE 8000

ENTRYPOINT CompileDaemon --build="go build main.go" --command=./main

要从上面的Dockerfile构建docker容器,请先使用下面的命令创建容器镜像:

docker build -t hello_world :1.0 .

可以在终端上使用docker ps -a查看机器上运行的容器。可以看到如下容器信息:

CONTAINER ID IMAGE COMMAND CREATED STATUS            PORTS NAMES
62cddf7c2659 hello_world:1.0 "/bin/sh -c 'Compile…" 23 minutes ago Exited (0) 23 minutes ago gifted_kilby

可以使用如下命令,运行容器:

docker run -p 8000:8000 hello_world:1.0

在浏览器上访问http://localhost:8000/。你应该看到{"data":"Hello World !"}。
让我们回顾Dockerfile中提到的Docker命令:

  • FROM:FROM表示容器使用的基础映像。golang:1.16是一个基于linux的镜像,它已经安装了golang,没有其他的程序或软件。

  • WORKDIR:WORKDIR修改工作目录。在我们的例子中是切换到 /app目录。它为后续命令创建一个工作目录。

  • ADD:ADD指令将文件从一个位置复制到另一个位置。ADD [SOURCE] [DESTINATION]是该命令的语法。类似地,还有一个COPY命令用于类似的目的。这里,我们先拷贝go.sum和go.mod文件这样在拷贝其他文件之前,我们就可以安装所有的依赖。

  • RUN:RUN指令将在当前镜像基础上执行任意命令,命令生成新的文件层并提交镜像。生成并提交的映像将用于Dockerfile的下一步。

  • EXPOSE:EXPOSE指示运行在Docker容器上的服务监听端口为8000。

  • ENTRYPOINT:一旦从镜像中创建容器,Entrypoint就会在容器内运行该命令。在Dockerfile中只能有一条Entrypoint指令。如果使用了多个Entrypoint指令,则只执行最后一条。在这里,一旦创建了容器,Entrypoint命令将运行我们的golang项目。

配置数据库

需要MySQL包来连接数据库和应用程序。使用以下命令安装它。

go get gorm.io/driver/mysql gorm.io/gorm

Gorm:是Golang中的一个功能强大的ORM库。因为连接数据库是应用程序的基本功能。让我们创建一个infrastructure文件夹并在此文件夹里创建db.go文件。写入如下代码:

package infrastructure

import (
"fmt"
"os"
"time"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)

//Database struct
type Database struct {
DB *gorm.DB
}

//NewDatabase : intializes and returns mysql db
func NewDatabase() Database {
USER := os.Getenv("DB_USER")
PASS := os.Getenv("DB_PASSWORD")
HOST := os.Getenv("DB_HOST")
DBNAME := os.Getenv("DB_NAME")

URL := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=True&loc=Local", USER, PASS,
HOST, DBNAME)
fmt.Println(URL)
db, err := gorm.Open(mysql.Open(URL))

if err != nil {
panic("Failed to connect to database!")

}
fmt.Println("Database connection established")
return Database{
DB: db,
}
}

在NewDatabase函数里面主要做了以下工作:
1、从环境变量中获取数据库账号密码等信息:USER, PASS, HOST和DBNAME。
2、将连接数据库URL使用环境变量拼装好,存在URL变量里。
3、根据URL使用gorm.Open方法来创建mysql连接。
4、最后,以gorm数据库实例作为参数返回database结构体,以后应用程序访问数据库就使用该结构体实例。

环境变量

项目中有两种类型的变量,程序变量和环境变量。程序变量是在代码块或模块中存储值的普通变量,而环境变量在整个项目中都是可用的。

设置环境变量

可以使用各种方法设置环境变量。这里,我们使用.env文件来设置它们。在项目根目录上创建.env文件,并在文件中添加以下变量。

MYSQL_ROOT_PASSWORD=root
DB_USER=user
DB_PASSWORD=Password@123
DB_HOST=db
DB_PORT=3306
DB_NAME=golang
SERVER_PORT=8000
ENVIRONMENT=
local

读取环境变量

现在,我们编写代码来读取.env文件。创建文件env。进入infracture文件夹,添加以下代码:

package infrastructure

import (
"log"
"github.com/joho/godotenv"
)

//LoadEnv loads environment variables from .env file
func LoadEnv() {
err := godotenv.Load(".env")
if err != nil {
log.Fatalf("unable to load .env file")
}
}

通过以下命令安装godotenv包:

go get github.com/joho/godotenv

为了加载.env文件和数据库,我们需要编辑main.go,如下所示:

func main() {

router := gin.Default()

router.GET("/", func(context *gin.Context) {
infrastructure.LoadEnv() //加载环境变量文件
infrastructure.NewDatabase() //创建数据库连接
context.JSON(http.StatusOK, gin.H{"data": "Hello World !"})
})
router.Run(":8000")
}

Docker Compose文件

我们需要docker compose来定义和运行多容器应用服务。在我们的案例中包括数据库和Gin应用程序。在项目中创建docker-compose.yml,并写入以下代码:

version: '3'
services:
db:
image: mysql/mysql-server:5.7
ports:
- "3305:3306"
environment:
- "MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}"
- "MYSQL_USER=${DB_USER}"
- "MYSQL_PASSWORD=${DB_PASSWORD}"
- "MYSQL_DATABASE=${DB_NAME}"
web:
build: .
ports:
- "8000:8000"
volumes:
- ".:/app"
depends_on:
- db
links:
- "db:database"

让我们回顾一下compose文件中提到的术语。

  • version:Docker compose版本

  • services:services部分定义了所有将创建的容器。这里我们有两个容器服务即web和db

  • web:这是Gin app服务的名称。可以自定义任意名称。Docker compose将使用这个名称创建容器。

  • build:这个术语指定了Dockerfile文件的位置,点号表示docker-compose.yml就在的当前目录。Dockerfile用于构建容器映像,根据它来运行容器。我们也可以在这里输入Dockerfile的路径。

  • ports:ports术语用于将容器端口映射或暴露给主机。这里映射端口“8000:8000”,这样我们就可以在主机的8000端口上访问我们的服务。

  • volumes:在这里,我们将代码文件目录附加到容器的./app目录,这样我们就不必为文件的每次更改去重新构建映像。这也有助于在调试模式下自动重新加载服务。

  • links:links就是将一个服务链接到另一个服务。这里,我们将数据库容器链接到web容器,这样我们的web容器就可以在bridge网中访问数据库。(提示! !)如果你想详细了解docker网络请参考:容器网络

  • image:如果我们没有Dockerfile,想要使用已经构建的镜像直接运行服务,我们可以使用' image '子句指定镜像位置。Compose将从指定位置拉镜像并从中创建一个容器。在我们的例子中,使用mysql/mysql-server:5.7镜像来创建数据库服务。

  • environment:需要在容器中设置任何环境变量都可以使用“environment”术语创建。

environment:
- "MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}"

${MYSQL_ROOT_PASSWORD}和其他变量是从.env中读取的。现在你已经准备好运行容器启动文件。使用以下命令开始构建并运行。

 docker-compose up --build

如果在你的终端上看到下面这样的信息,这意味着你的服务器已经运行:

db_1 | 2021-05-24T09:18:02.841094Z 0 [Note] Event Scheduler: Loaded 0 events
db_1 | 2021-05-24T09:18:02.841370Z 0 [Note] mysqld: ready for connections.
db_1 | Version: '5.7.34' socket: '/var/lib/mysql/mysql.sock' port: 3306 MySQL Community Server (GPL)
web_1 | 2021/05/24 09:18:06 Running build command!
web_1 | 2021/05/24 09:18:17 Build ok.
web_1 | 2021/05/24 09:18:17 Restarting the given command.
web_1 | 2021/05/24 09:18:17 stdout: [GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
web_1 | 2021/05/24 09:18:17 stdout:
web_1 | 2021/05/24 09:18:17 stdout: [GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
web_1 | 2021/05/24 09:18:17 stdout: - using env: export GIN_MODE=release
web_1 | 2021/05/24 09:18:17 stdout: - using code: gin.SetMode(gin.ReleaseMode)
web_1 | 2021/05/24 09:18:17 stdout:
web_1 | 2021/05/24 09:18:17 stdout: [GIN-debug] GET / --> main.main.func1 (3 handlers)
web_1 | 2021/05/24 09:18:17 stdout: [GIN-debug] Listening and serving HTTP on :8000

总结

以上就是本系列的第一部分。下一部分重点:

  • 向应用程序添加模型(结构)

  • 采用领域驱动开发模式

请关注我以获取下一部分的更新或订阅,这样您就不会错过我即将发表的文章。谢谢你! !