你的下一个 Golang 项目的终极设置

2025-05-25

你的下一个 Golang 项目的终极设置

注:本文最初发布于martinheinz.dev

替代文本

对我来说,启动新项目时最大的挑战一直是试图将项目设置得“完美”。我总是尝试使用最佳的目录结构,以便所有内容易于查找,导入功能也运行良好;设置所有命令,以便我始终只需单击/执行一次命令即可完成所需的操作;找到最适合我所用语言/库的 linter、格式化程序和测试框架……

这个列表还在继续,但我从来没有真正对设置感到满意...除了这个针对Golang的终极和最佳(IMHO)设置!

注意:此设置运行良好,部分原因是它基于现有项目,可在此处此处找到。

TL;DR:这是我的存储库 - https://github.com/MartinHeinz/go-project-blueprint

目录结构

首先,我们来看一下项目的目录结构。有几个顶级文件以及 4 个目录:

  • pkg- 先从简单开始 -pkg是一个只包含全局版本字符串的Go包。它会替换构建过程中根据提交哈希计算出的实际版本。
  • config- 接下来是配置目录,其中包含所有必要的环境变量文件。可以使用任何文件类型,但我推荐使用YAML文件,因为它们更易于阅读。
  • build- 此目录包含构建和测试应用程序以及为代码分析工具生成报告所需的所有 shell 脚本。
  • cmd- 实际源代码!按照惯例,源目录名为cmd,其中还有另一个名为项目名称的目录——在本例中为blueprint。接下来,在这个目录中是一个main.go运行整个应用程序的 ,以及所有其他按模块划分的源文件(稍后会详细介绍)。

注意:从一些反馈中,我发现很多人喜欢使用internalpkg目录来存放所有源代码。我个人认为这没有必要,而且多余,因此我把所有内容都放在 中cmd,但每个人都有自己的选择。

除了目录之外,还有相当多的文件,我们将在以下章节中讨论这些文件。

Go Modules 实现完美的依赖管理

Go项目使用各种各样的依赖管理策略。但是,从 1.11 版本开始,Go有了名为Go modules 的官方依赖管理解决方案
我们所有的依赖项都列在go.mod文件中,该文件可以在根目录中找到。它可能如下所示:

module github.com/MartinHeinz/go-project-blueprint

go 1.12

require (
    github.com/spf13/viper v1.4.0
    github.com/stretchr/testify v1.4.0
)
Enter fullscreen mode Exit fullscreen mode

你可能会问“文件是如何填充依赖项的?”嗯,这很简单,你只需要一个命令:

go mod vendor
Enter fullscreen mode Exit fullscreen mode

go.mod此命令重置主模块的供应商目录,以包含根据文件和 Go 源代码的状态构建和测试所有模块包所需的所有包。

实际源代码和配置

现在我们终于可以开始处理源代码了。如上所述,源代码被划分为多个模块。每个模块都是源代码根目录下的一个目录。每个模块中都有源文件及其测试文件,例如:

./cmd/
└── blueprint
    ├── apis <- Module
    │   ├── apis_test.go
    │   ├── user.go
    │   └── user_test.go
    ├── daos <- Module
    │   ├── user.go
    │   └── user_test.go
    ├── services <- Module
    │   ├── user.go
    │   └── user_test.go
    ├── config <- Module
    │       └── config.go
    └── main.go

Enter fullscreen mode Exit fullscreen mode

这种结构有助于提高代码的可读性和可维护性,因为它将代码划分成合理的块,更容易遍历。至于配置,在这个设置中,我使用了Viper,它是一个 Go配置库,可以处理各种格式、命令行标志、环境变量等。
那么我们在这里如何使用它(Viper)呢?让我们看一下config包:


var Config appConfig

type appConfig struct {
    // Example Variable, which is loaded in LoadConfig function
    ConfigVar string
}

// LoadConfig loads config from files
func LoadConfig(configPaths ...string) error {
    v := viper.New()
    v.SetConfigName("example")  // <- name of config file
    v.SetConfigType("yaml")
    v.SetEnvPrefix("blueprint")
    v.AutomaticEnv()
    for _, path := range configPaths {
        v.AddConfigPath(path)  // <- // path to look for the config file in
    }
    if err := v.ReadInConfig(); err != nil {
        return fmt.Errorf("failed to read the configuration file: %s", err)
    }
    return v.Unmarshal(&Config)
}

Enter fullscreen mode Exit fullscreen mode

这个包由一个文件组成。它声明了一个文件struct,用于保存所有配置变量,并包含一个加载配置的函数。它接受配置文件的路径,在本例中,我们将使用位于项目根目录并包含我们上面提到的YAMLLoadConfig文件的目录路径。那么如何使用它呢?首先运行它configmain.go

if err := config.LoadConfig("./config"); err != nil {
    panic(fmt.Errorf("invalid application configuration: %s", err))
}
Enter fullscreen mode Exit fullscreen mode

简单快速的测试

代码本身之后第二重要的事情是什么?质量测试。为了愿意编写大量好的测试,您需要一个可以让您轻松做到这一点的设置。为了实现这一点,我们将使用Makefile名为的目标test,它收集并运行cmd子目录中的所有测试(所有带有_test.go后缀的文件)。这些测试也会被缓存,因此只有相关代码发生一些更改时才会运行它们。这一点至关重要,因为如果测试太慢,您(很可能)最终会停止运行和维护它们。除了单元测试之外,它还make test可以帮助您维护一般代码质量,因为它也会在每次测试运行时运行gofmtgo vet强制go fmt您正确格式化代码并go vet使用启发式方法查找任何可疑的代码构造。示例输出:

foo@bar:~$ make test
Running tests:
ok      github.com/MartinHeinz/go-project-blueprint/cmd/blueprint   (cached)
?       github.com/MartinHeinz/go-project-blueprint/cmd/blueprint/config    [no test files]
?       github.com/MartinHeinz/go-project-blueprint/pkg [no test files]

Checking gofmt: FAIL - the following files need to be gofmt'ed:
    cmd/blueprint/main.go

Checking go vet: FAIL
# github.com/MartinHeinz/go-project-blueprint/cmd/blueprint
cmd/blueprint/main.go:19:7: assignment copies lock value to l: sync.Mutex

Makefile:157: recipe for target 'test' failed
make: *** [test] Error 1
Enter fullscreen mode Exit fullscreen mode

始终在 Docker 中运行

人们常说“它在我的机器上能用(但在云端不行)……”,为了避免这种情况,我们有一个很简单的解决方案——始终在 Docker 容器中运行。我说的始终,其实是——在容器中构建、在容器中运行、在容器中测试。其实我在上一节中没有提到,但实际上“只是”make test如此 docker run

那么,它在这里是如何工作的呢?首先,Dockerfiles我们在项目的根目录中有两个文件,一个用于测试(test.Dockerfile),一个用于运行应用程序(in.Dockerfile):

  • test.Dockerfile- 理想情况下,我们只需要一个 Dockerfile 来运行和测试应用程序。但是,在运行测试时,可能需要对环境进行一些调整。这就是为什么我们在这里提供这个镜像的原因——允许我们安装额外的工具和库,以备测试需要。例如,假设我们有一个要连接的数据库。我们不想在每次测试运行时都启动整个PostgreSQL服务器,也不想依赖于主机上运行的某个数据库。因此,我们可以改用SQLite内存数据库来运行测试。但是,你猜怎么着?SQLite二进制文件需要CGO。那么,我们该怎么做呢?我们只需安装gccg++启用 CGOCGO_ENABLED即可。

  • in.Dockerfile- 如果你在代码库中查看它Dockerfile,你会发现它只是一堆参数,并将配置复制到镜像中。那么,这里面到底发生了什么?它只在我们运行 时in.Dockerfile从 中使用Makefile,参数就是从那里填充的make container。现在,是时候看看 本身了,它为我们Makefile完成了所有工作。👇docker

使用 Makefile 将所有内容整合在一起

很长一段时间以来,它们Makefiles对我来说似乎很可怕,因为我只见过它们在C代码中使用,但它们并不可怕,而且可以用于很多事情,包括这个项目!现在让我们来探索一下我们的目标Makefile

  • make build- 工作流程中的第一步 - 应用程序构建 - 它在bin目录中构建二进制可执行文件:
@echo "making $(OUTBIN)"
  @docker run                                              \ # <- It's just a `docker run`
    -i                                                     \ #    command in disguise  
    --rm                                                   \ # <- Remove container when done
    -u $$(id -u):$$(id -g)                                 \ # <- Use current user
    -v $$(pwd):/src                                        \ # <- Mount source folder
    -w /src                                                \ # <- Set workdir
    -v $$(pwd)/.go/bin/$(OS)_$(ARCH):/go/bin               \ # <- Mount directories where
    -v $$(pwd)/.go/bin/$(OS)_$(ARCH):/go/bin/$(OS)_$(ARCH) \ #    binary will be outputted
    -v $$(pwd)/.go/cache:/.cache                           \
    --env HTTP_PROXY=$(HTTP_PROXY)                         \
    --env HTTPS_PROXY=$(HTTPS_PROXY)                       \
    $(BUILD_IMAGE)                                         \
    /bin/sh -c "                                           \ # <- Run build script
        ARCH=$(ARCH)                                       \ #    (Checks for presence
        OS=$(OS)                                           \ #    of arguments, sets
        VERSION=$(VERSION)                                 \ #    env vars and runs
        ./build/build.sh                                   \ #    `go install`)
    "
  @if ! cmp -s .go/$(OUTBIN) $(OUTBIN); then \ # <- If binaries have changed 
   mv .go/$(OUTBIN) $(OUTBIN);               \ #    move them from `.go` to `bin`
   date >$@;                                 \
  fi
Enter fullscreen mode Exit fullscreen mode
  • make test- 下一个是测试 - 它再次使用docker run几乎相同的脚本,唯一的区别是test.sh脚本(仅相关部分):
TARGETS=$(for d in "$@"; do echo ./$d/...; done)

go test -installsuffix "static" ${TARGETS} 2>&1

ERRS=$(find "$@" -type f -name \*.go | xargs gofmt -l 2>&1 || true)

ERRS=$(go vet ${TARGETS} 2>&1 || true)
Enter fullscreen mode Exit fullscreen mode

以上几行是该文件的重要部分。第一行使用给定的路径作为参数收集测试目标。第二行运行测试并将输出打印到标准输出。剩下的两行分别运行go fmtgo vet,收集错误(如果有)并打印出来。

  • make container- 现在,最重要的部分 - 创建可部署的容器:
.container-$(DOTFILE_IMAGE): bin/$(OS)_$(ARCH)/$(BIN) in.Dockerfile
    @sed                                 \
        -e 's|{ARG_BIN}|$(BIN)|g'        \
        -e 's|{ARG_ARCH}|$(ARCH)|g'      \
        -e 's|{ARG_OS}|$(OS)|g'          \
        -e 's|{ARG_FROM}|$(BASEIMAGE)|g' \
        in.Dockerfile > .dockerfile-$(OS)_$(ARCH)
    @docker build -t $(IMAGE):$(TAG) -t $(IMAGE):latest -f .dockerfile-$(OS)_$(ARCH) .
    @docker images -q $(IMAGE):$(TAG) > $@
Enter fullscreen mode Exit fullscreen mode

这个目标的代码非常简单,它首先替换变量,in.Dockerfile然后运行生成带有“dirty”“latest”docker build标签的镜像。最后,它将容器名称打印到标准输出。

  • make push- 接下来,有了镜像之后,我们需要把它存储到某个地方,对吧?所以,我们只需make push要将镜像推送到Docker镜像仓库即可。
  • make ci- 另一个很好的用途Makefile是在我们的 CI/CD 流水线中利用它(下一节)。这个目标与 非常相似make test- 它也运行所有测试,但最重要的是,它还生成覆盖率报告,然后将其用作代码分析工具的输入。
  • make clean- 最后,如果我们想清理我们的项目,我们可以运行make clean,它会删除以前目标生成的所有文件。

我将省略其余部分,因为它们对于正常的工作流程来说不是必需的,或者只是其他目标的一部分。

CI/CD 带来极致编码体验

最后,但绝对不是最不重要的——CI/CD。既然有这么好的设置(如果我这么说的话),如果忽略一些可以帮我们做很多事情的花哨管道,那真是太可惜了,对吧?我不会过多地介绍管道里的内容,因为你可以自己在这里查看(我几乎为每一行都添加了注释,所以一切都解释清楚了),但我想指出几点:

此次Travis构建使用Matrix Build和 4 个并行作业来加速整个过程

  • 这里的 4 个部分(工作)是:
    • 构建和测试,验证应用程序是否按预期工作
    • SonarCloud,我们在其中生成覆盖率报告并将其发送到SonarCloud服务器
    • CodeClimate - 这里,与上一个一样 - 我们生成报告并将其发送,这次使用他们的测试报告器发送给CodeClimate
    • 推送到注册表- 最后,我们将容器推送到GitHub 注册表(请继续关注该博客文章!)

结论

希望这篇文章能在您未来的Go编程之旅中有所帮助。如果您想了解更多详情,请点击此处查看代码库。此外,如果您有任何反馈或改进想法,请随时提交问题、fork 代码库或点个星,这样我才知道继续改进是有意义的。🙂

rest-api在下一部分中,我们将研究如何扩展此蓝图以轻松构建 RESTful API、使用内存数据库进行测试以及设置 swagger 文档(您可以在存储库中的分支中预览)。

文章来源:https://dev.to/martinheinz/ultimate-setup-for-your-next-golang-project-5dae
PREV
使用 Notion 作为 Next.JS 博客的数据库
NEXT
是时候和 Docker 说再见了