本课程将从零开始,逐步引导你开发一个完整的 Go 项目。通过这种方式,来降低你学习 Go 项目开发的难度。此外,本课程还将详细介绍我在开发过程中的思考和实战经验,帮助你理解代码实现背后的原因,从而让你更透彻地掌握 Go 项目开发。在我看来这些开发经验和设计思考要比其中的冷知识更有价值。
提示:本节课最终源码位于 miniblog 项目的 feature/s01 分支。
如何初始化一个 Go 项目仓库
Go 项目开发的第一步是初始化一个 Go 项目仓库。根据开发能力、项目类别、项目需求等,可以选择不同的项目初始化方式。一般而言有以下几种项目初始化方式,具体如下表所示。
不同项目的初始化方式适用的场景、难度及其带来的效果各不相同。本课程旨在向读者展示 Go 项目开发的流程、细节及相关思考,因此选择了从零初始化并开发一个完整的 Go 项目。
生成工具
初始化 Go 项目最简单的方法是使用工具进行初始化。通过这些脚手架工具,可以快速生成一个 Go 项目模板,并基于生成的项目模板进行开发。目前业界有许多项目生成工具,例如 osctl、kratos、nirvana、sponge 等。使用这些工具初始化 Go 项目的最大优点是方便、快捷,且能够生成相对高质量的项目模板。然而,缺点也很明显:生成的 Go 项目模板的代码质量、目录结构、代码架构、功能列表及功能构建方式均依赖于工具的实现。
本课程介绍的 miniblog 项目也有其匹配的项目生成工具 osctl,osctl 工具可以生成跟 miniblog 项目一致的代码。这些代码从代码质量、目录结构、简洁架构、设计思路及可选的功能列表等都跟 miniblog 保持一致。
复制已有项目
除了使用脚手架工具快速生成 Go 模板项目外,还可以直接复制一个已有的 Go 项目,然后修改项目的仓库名和 Go 包导入路径,替换与原项目名相关的字符串等方式来初始化一个 Go 项目。这种方式的缺点是改造工作量较大。优点是非常灵活,可以根据需求选择喜欢的 Go 项目,魔改之后,形成自己的 Go 项目。
我在开发一个新项目时,最常用的方法是复制已有的 Go 项目,然后修改项目名称和 Go 包导入路径,解决编译问题后形成自己的 Go 项目。我喜欢这种方式的主要原因是其灵活性,可以根据需要选择代码质量高、设计合理、功能列表完整或具备所需功能的 Go 项目。
例如,在开发企业应用时,我会选择复制并魔改 miniblog 项目来初始化一个新的 Go 项目。如果新项目功能复杂,我则会使用 onex 项目来初始化一个新的项目。魔改已有项目为一个新项目时,可以使用 Linux 命令批量修改,修改命令通常如下:
$ cp -a miniblog myproj && cd myproj # 复制 miniblog 项目为新的项目名:myproj
# 替换 Go 包导入路径
$ sed -i 's/github.com\/onexstack\/miniblog/github.com\/onexstack\/myproj/g' `grep -Rl github.com/onexstack/miniblog *`
$ sed -i 's/MiniBlog/MyProj/g' `grep -Rl MiniBlog *` # 替换大写的原项目标识符
$ sed -i 's/miniblog/myproj/g' `grep -Rl miniblog *` # 替换所有的 miniblog 项目标识符
# 查找并将 miniblog 目录改成新的项目名 myproj
$ find . -type d -name 'miniblog' -execdir mv {} myproj \;
$ make build从零开发
还可以选择从零初始化一个 Go 项目。本课程为了详细介绍 Go 项目的开发步骤及方法,选择了从零初始化一个 Go 项目。这种方式最大的缺点是工作量很大,项目目录结构、项目源码、项目代码结构等都需要从零设计并开发。但优点也很明显,可以完全根据自己的需求来设计和开发。
选择合适的项目初始化方法
在实际开发中,可以根据需要选择合适的项目初始化方法。如果脚手架工具生成的代码,在目录结构设计、开发风格、代码质量、架构设计、功能列表等方面能够满足你的需求,则可以优先考虑使用工具来快速初始化 Go 项目。如果工具不能满足需求,建议基于一个优秀的 Go 项目进行魔改,例如,可以魔改 miniblog 项目为一个新的项目。
如果你喜欢 miniblog 项目的设计及开发方式,那么可以选择 octl 工具,来快速初始化一个跟 miniblog 一致的新项目。除非必要,否则不建议从零开发一个 Go 项目,因为开发工作量大,并且项目的质量往往不如优秀的开源项目高。
初始化项目仓库
开始 Go 项目开发的第一步便是初始化一个项目仓库。对于 Go 项目而言,主要包括以下初始化内容:
- 创建项目目录;
- 初始化目录为 Go 模块;
- 初始化目录为 Git 仓库;
- 创建需要的目录;
- 创建 Hello World 程序。
创建项目目录
在开始初始化项目之前,需先设计好项目的名称。一个简洁、易懂且合适的项目名是开发高质量 Go 项目的第一步。通常,需要提前确认以下名称:
- 项目名称:项目名要具有一定语义,说明该项目的功能,建议的格式为纯小写的精短名字。如果项目名字过长,可以按单词用中杠线(-)分割,但最好不要使用。以下是一些合格的名字:api、controllermanager、controller-manager。不建议命名为:controller_manager;
- 项目名称大小写:还需确认项目名在代码中的大小写格式。统一大小写格式可以使整个代码命名格式保持统一。例如:controller-manager/controllermanager 项目的小写格式为 controllermanager,大写格式为 ControllerManager;
- 项目名简写格式:有些项目名出于易读性考虑,可能会较长。在编写代码时,如果引用了项目名,可能会导致代码行过长,为了使代码行简短易读,通常会采用简写模式。带中杠线分割的项目名的简短模式一般为每个单词的首字母,例如:controller-manager 为 cm。不带中杠线分割的项目名简写模式需要根据具体名字确定,且没有统一的命名规则,例如:controller 可以简写为 ctrl。
一个好的名字是一个好的开始。本课程实战项目的项目名字为 miniblog,意为微博客。代码中的大写格式为 MiniBlog,小写格式为 miniblog。简写格式为 mb。
确定好项目名字后,接下来可以在指定的组织目录下创建项目目录。我将在 onexstack 组织下创建 miniblog 项目目录。onexstack 组织是一个专门进行 Go 实战教学的 GitHub 组织,里面包含了大量优秀的 Go 实战项目,例如 OneX、EasyAI 等项目。如果你对这些项目感兴趣,可以访问 onexstack/community 来查看这些项目。
通过执行以下命令来创建目录,并在项目目录中添加一个 README.md 文件:
$ mkdir -p $GOPATH/src/github.com/onexstack/miniblog
$ cd $GOPATH/src/github.com/onexstack/miniblog
$ echo '## miniblog 项目' >> README.md提示:你可以使用 https://readme.so 工具来协助生成 README 文件,通常 README 文件需要包含以下部分:Features、Installation、Usage/Examples、Documentation、Feedback、Contributing、Authors、License、Related。
初始化目录为 Go 模块
miniblog 是一个 Go 项目,根据 Go 语法要求,还需要将该项目初始化为一个 Go 模块,并添加到 Go 工作区中。初始化命令如下:
$ go mod init # 初始化当前项目为一个 Go 模块
$ go work init . # 初始化当前目录为一个 Go 工作区
$ go work use . # 添加当前模块到 Go 工作区初始化目录为 Git 仓库
当前项目开发基本上都是使用 Git 来管理项目源码,因此,还需要将项目仓库初始化为一个 Git 仓库。
在提交代码时,有些文件,例如备份文件、临时文件和日志文件不需要提交到项目仓库中,可以通过在项目目录下添加 .gitignore 文件来忽略这些文件。如果你不知道如何配置 .gitignore 文件中的内容,可以借助 .gitignore 文件生成工具来自动生成一个可用的 .gitignore 文件,例如 gitignore.io 就是一个很好用的 .gitignore 文件生成工具。miniblog 项目使用的 .gitignore 文件如下述代码所示。
# 备份文件
*.bak
*~
# Go 工作区文件。Go 项目开发中,不建议将 Go 工作区文件提交到代码仓库
go.work
go.work.sum
# 日志文件
*.log
# 自定义文件
/_output可以执行以下命令将 Go 项目仓库初始化为一个 Git 仓库:
$ git init # 初始化当前目录为 Git 仓库
$ git config user.name 孔令飞 # 设置仓库级别用户名
$ git config user.email [email protected] # 设置仓库级别邮箱
$ git config --global credential.helper store # 永久保存凭据
$ git add . # 添加所有被 Git 追踪的文件到暂存区
$ git remote add origin https://github.com/onexstack/miniblog # 将本地仓库与远程仓库相关联
$ git commit -m "feat: 第一次提交" # 将暂存区内容添加到本地仓库中之后,可以在 miniblog 目录下进行代码开发,并根据需要提交代码。提交后的源码目录内容如下:
$ ls -AF
.git/ .gitignore go.mod go.work README.md创建需要的目录
初始化代码仓库后,我们可以根据前面设计的目录规范创建一些空目录,例如:
$ mkdir -p cmd configs docs scripts
$ ls -F
cmd/ configs/ docs/ go.mod go.work README.md scripts/提前创建一些符合目录规范的空目录,可以带来以下好处:
- 提前规划目录相当于提前规划未来的功能,将未来要实现的功能以目录的形式固化在项目仓库中,起到记录的作用;
- 提前创建目录有利于后续文件按照功能存放在预先规划好的目录中,从而使项目更加规范。否则,不同开发者可能会根据各自的开发习惯,创建各种各样的目录结构和目录名称。
例如,可以将之前设计好的规范存放在 docs/devel/zh-CN/conversions 目录中。因为 Git 不追踪空目录,为了让 Git 追踪空目录,我们可以在空目录下创建一个空文件.keep,并在适当的时候执行以下命令以删除这些临时.keep 文件:
$ find . -name .keep | xargs -i rm {}miniblog 项目的完整目录结构说明见 docs/devel/zh-CN/directory.md 文件。
创建 Hello World 程序
一个全新的项目,需要先编写一个最简单的 Hello World 程序,以检查开发和编译环境是否就绪。根据目录规范,需要在 cmd/mb-apiserver 目录下创建 main.go 文件,内容如下:
package main
import "fmt"
// Go 程序的默认入口函数。阅读项目代码的入口函数.
func main() {
fmt.Println("Hello World!")
}执行以下命令编译并运行此 Hello World 程序:
# gofmt 用来格式化当前目录及其子目录中的所有 Go 源文件
$ gofmt -s -w ./
# go build 命令用来编译Go源码
# -o 指定输出的可执行文件名称
# -v 选项用于显示详细的编译过程信息
$ go build -o _output/mb-apiserver -v cmd/mb-apiserver/main.go
# 运行编译生成的可执行文件
$ _output/mb-apiserver
Hello World!上述命令,成功编译并运行了 mb-apiserver 二进制文件,这说明我们的开发、编译环境已就绪。以下两点需要注意:
- 很多 Go 项目会将 main 文件名与应用程序名字保持一致,例如:miniblog.go。但我在日常开发中,更喜欢将 main 文件命名为 main.go,原因是这样的文件名能够明确告诉其他开发者这是一个 main 文件;
- 我们通常需要将构建产物、临时文件等存放在一个单独的目录中,例如:_output。这样能够方便找到并使用这些构建产物,或安全地删除这些构建产物,例如,只需执行 rm -rf _output 即可删除这些构建产物。此外,我们还可以将 _output 目录加入到 .gitignore 文件中,告诉 Git 不要追踪这些临时文件。
main 文件所在的目录 mb-apiserver 也是 miniblog APIServer 的组件名。在 Go 项目开发中,通常在组件名中包含项目的简写前缀 mb-,通过 mb- 前缀,可以很容易地辨别该组件是 miniblog 的 APIServer 组件。此外,通过 mb- 前缀,也可以有效避免组件名与其他项目的组件名发生冲突。
小结
本节课将通过从零开始的方式,让你全面了解一个 Go 项目的开发流程,包括目录结构、代码组织和思考过程等,这种自定义开发方式虽工作量较大,但能让你在实践中全面提升对 Go 项目开发本质的理解。
初始化项目的方法主要有三种:借助脚手架工具、复制已有项目以及从零开始搭建。使用工具最快捷省事,但需要接受工具的目录结构与架构约束;复制已有项目灵活性高,可以直接基于成熟项目进行“魔改”;从零开始初始化工作量最大,但可完全根据需求进行设计。
完善的项目初始化通常包括:设计并创建项目目录、将目录初始化为 Go 模块和 Git 仓库、配置 .gitignore 忽略文件,以及根据需求提前创建空目录。此举既能规划好后续功能,也能让整个项目更有条理。
完成基本初始化后,可以先编写简单的 Hello World 程序并进行编译测试,以确保开发环境正常,并验证目录结构、命名规范是否符合预期。主入口文件(main.go)与输出目录(_output)等细节安排,则有助于后续持续集成和项目管理。