type
status
date
slug
summary
tags
category
icon
password
Sub-item
Last edited time
Nov 28, 2024 07:30 AM
Parent item
领域
3.1 应用开发目录
开发的代码包含前端代码和后端代码,可以分别存放在前端目录和后端目录中。
3.1.1 /web
前端代码存放目录,主要用来存放 Web 静态资源,服务端模板和单页应用(SPAs)。
3.1.2 /cmd
一个项目有很多组件,可以把组件 main 函数所在的文件夹统一放在 /cmd 目录下,例如:
这里要保证
/cmd/<组件名>
目录下不要存放太多的代码,如果你认为代码可以导入并在其他项目中使用,那么它应该位于 /pkg
目录中。如果代码不是可重用的,或者你不希望其他人重用它,请将该代码放到 /internal
目录中。3.1.3 /internal
存放私有应用和库代码。如果一些代码,你不希望在其他应用和库中被导入,可以将这部分代码放在/internal 目录下。
internal 目录下直接存放每个组件的源码目录(一个项目可以由一个或多个组件组成),当项目变大、组件增多时,可以将新增加的组件代码存放到 internal 目录,这时 internal 目录就是可扩展的。例如:
/internal
目录建议包含如下目录:/internal/apiserver
:该目录中存放真实的应用代码。这些应用的共享代码存放在 /internal/pkg
目录下。
/internal/pkg
:存放项目内可共享,项目外不共享的包。这些包提供了比较基础、通用的功能,例如工具、错误码、用户验证等功能。
建议是,一开始将所有的共享代码存放在 /internal/pkg 目录下,当该共享代码做好了对外开发的准备后,再转存到 /pkg 目录下。放入 internal 的包从 go 机制上就无法被外部引用。
3.1.4 /pkg
该目录中存放可以被外部应用使用的代码库,其他项目可以直接通过 import 导入这里的代码。所以,我们在将代码库放入该目录时一定要慎重。
3.1.5 /vendor
项目依赖,可通过
go mod vendor
创建。需要注意的是,如果是一个 Go 库,不要提交 vendor 依赖包。vendor 就是把依赖的代码 clone 一份,放在 vendor 中,这样构建时,go 编译器会使用 vendor 中的依赖,而不是到网上去下载,也不会使用本地 module cache 中的依赖。3.1.6 /third_party
外部帮助工具,分支代码或其他第三方应用(例如 Swagger UI)。比如我们 fork 了一个第三方 go 包,并做了一些小的改动,我们可以放在目录
/third_party/forked
下。一方面可以很清楚的知道该包是 fork 第三方的,另一方面又能够方便地和 upstream 同步。3.2 Go 应用测试目录
3.2.1 /test
用于存放其他外部测试应用和测试数据。
/test
目录的构建方式比较灵活:对于大的项目,可以创建数据子目录。例如,如果需要 Go 忽略该目录中的内容,可以使用 /test/data
或 /test/testdata
目录。需要注意的是,Go 也会忽略以 . 或 _ 开头的目录或文件。这样在命名测试数据目录方面,可以具有更大的灵活性。
3.3 Go 应用部署目录
3.3.1 /configs
这个目录用来配置文件模板或默认配置。例如,可以在这里存放
confd
或 consul-template
模板文件。这里有一点要注意,配置中不能携带敏感信息,这些敏感信息,我们可以用占位符来替代,例如:3.3.2 /deployments
用来存放 Iaas、PaaS 系统和容器编排部署配置和模板(Docker-Compose,Kubernetes/Helm,Mesos,Terraform,Bosh)。在一些项目,特别是用 Kubernetes 部署的项目中,这个目录可能命名为
deploy
。3.3.3 /init
存放初始化系统(systemd,upstart,sysv)和进程管理配置文件(runit,supervisord)。比如
sysemd
的 unit
文件。这类文件,在非容器化部署的项目中会用到。3.4 Go 应用项目管理目录
3.4.1 /Makefile
一个 Go 项目在其根目录下应该有一个 Makefile 工具,用来对项目进行管理,Makefile 通常用来执行静态代码检查、单元测试、编译等功能。其他常见功能:
- 静态代码检查(lint):推荐用 golangci-lint。
- 单元测试(test):运行
go test ./...
。
- 编译(build):编译源码,支持不同的平台,不同的 CPU 架构。
- 镜像打包和发布(image/image.push):现在的系统比较推荐用 Docker/Kubernetes 进行部署,所以一般也要有镜像构建功能。
- 清理(clean):清理临时文件或者编译后的产物。
- 代码生成(gen):比如要编译生成 protobuf pb.go 文件。
- 部署(deploy),可选:一键部署功能,方便测试。
- 发布(release):发布功能,比如:发布到 Docker Hub、github 等。
- 帮助(help):告诉 Makefile 有哪些功能,如何执行这些功能。
- 版权声明(add-copyright):如果是开源项目,可能需要在每个文件中添加版权头,这可以通过 Makefile 来添加。
- API 文档(swagger):如果使用 swagger 来生成 API 文档,这可以通过 Makefile 来生成。
建议:直接执行 make 时,执行如下各项 format -> lint -> test -> build,如果是有代码生成的操作,还可能需要首先生成代码 gen -> format -> lint -> test -> build。
3.4.2 /scripts
该目录主要用来存放脚本文件,实现构建、安装、分析等不同功能。不同项目,里面可能存放不同的文件,但通常可以考虑包含以下 3 个目录:
- /scripts/make-rules:用来存放 makefile 文件,实现 /Makefile 文件中的各个功能。Makefile 有很多功能,为了保持它的简洁,我建议你将各个功能的具体实现放在 /scripts/make-rules 文件夹下
- /scripts/lib:shell 库,用来存放 shell 脚本。一个大型项目中有很多自动化任务,比如发布、更新文档、生成代码等,所以要写很多 shell 脚本,这些 shell 脚本会有一些通用功能,可以抽象成库,存放在 /scripts/lib 目录下,比如 logging.sh,util.sh 等。
- /scripts/install:如果项目支持自动化部署,可以将自动化部署脚本放在此目录下。如果部署脚本简单,也可以直接放在 /scripts 目录下。 另外,shell 脚本中的函数名,建议采用语义化的命名方式,例如 iam::log::info 这种语义化的命名方式,可以使调用者轻松的辨别出函数的功能类别,便于函数的管理和引用。
3.4.3 /build
这里存放安装包和持续集成相关的文件。这个目录下有 3 个大概率会使用到的目录,在设计目录结构时可以考虑进去。
- /build/package:存放容器(Docker)、系统(deb, rpm, pkg)的包配置和脚本。
- /build/ci:存放 CI的配置文件和脚本。
- /build/docker:存放子项目各个组件的 Dockerfile 文件。
3.4.4 /tools
存放这个项目的支持工具。这些工具可导入来自 /pkg 和 /internal 目录的代码。
3.4.5 /githooks
Git 钩子。比如,我们可以将 commit-msg 存放在该目录。
3.4.6 /assets
项目使用的其他资源 (图片、CSS、JavaScript 等)。
3.4.7 /website
如果你不使用 Github 页面,则在这里放置项目的网站数据。
3.5 Go 应用文档目录
3.5.1 /README.md
项目的 README 文件一般包含了项目的介绍、功能、快速安装和使用指引、详细的文档链接以及开发指引等。
3.5.2 /docs
存放设计文档、开发文档和用户文档等(除了 godoc 生成的文档)。推荐存放以下几个子目录:
- /docs/devel/{en-US,zh-CN}:存放开发文档、hack 文档等。
- /docs/guide/{en-US,zh-CN}: 存放用户手册,安装、quickstart、产品文档等,分为中文文档和英文文档。
- /docs/images:存放图片文件。
3.5.3 /CONTRIBUTING.md
开源就绪的项目,用来说明如何贡献代码,如何开源协同等等。CONTRIBUTING.md 不仅能够规范协同流程,还能降低第三方开发者贡献代码的难度。
3.5.4 /api
/api 目录中存放的是当前项目对外提供的各种不同类型的 API 接口定义文件,其中可能包含类似 /api/protobuf-spec、/api/thrift-spec、/api/http-spec、openapi、swagger 的目录,这些目录包含了当前项目对外提供和依赖的所有 API 文件。
3.5.5 /LICENSE
版权文件可以是私有的,也可以是开源的。常用的开源协议有:Apache 2.0、MIT、BSD、GPL、Mozilla、LGPL。
3.5.6 /CHANGELOG
当项目有更新时,为了方便了解当前版本的更新内容或者历史更新内容,需要将更新记录存放到 CHANGELOG 目录。
3.5.7 /examples
存放应用程序或者公共包的示例代码
4. 实际项目参考目录
5. Makefile 的规则
Makefile 基本格式如下:
其中:
- target:编译文件要生成的目标
- prerequisites:编译文件需要的依赖
- command:依赖生成目标所需要执行的命令(任意的 shell 命令),Makefile 中的命令必须以 [tab] 开头
6.1 实战 1
上面的 Makefile 文件中,
.PHONY 是个伪目标,形式上是一个目标,但是不需要依赖,伪目标一般只是为了执行目标下面的命令(比如 clean 就是伪目标)。
@ 放在行首,表示不打印此行。默认在编译的过程中,会把此行的展开效果字符串打印出来。
上面的 Makefile 实现了如下功能:
make
:执行 go build -v .
生成 Go 二进制文件
make gotool
:执行 gofmt -w .
和 go tool vet .
(格式化代码和源码静态检查)
make clean
:做一些清理工作:删除二进制文件、删除 vim swp 文件
make ca
:生成证书
make help
:打印 help 信息其中
gitTag
、gitCommit
、gitTreeState
等变量的值是通过 -ldflags -X importpath.name=value
在编译时传到程序中的。为此我们需要在编译时传入这些信息,并在 go build
中添加这些 flag
。-w
为去掉调试信息(无法使用 gdb 调试),这样可以使编译后的二进制文件更小。我们可以将这些信息写在配置文件中,程序运行时从配置文件中取得这些信息进行显示。但是在部署程序时,除了二进制文件还需要额外的配置文件,不是很方便。或者将这些信息写入代码中,这样不需要额外的配置,但要在每次编译时修改代码文件,也比较麻烦。Go 官方提供了一种更好的方式:通过
-ldflags -X importpath.name=value
(详见 -ldflags -X importpath.name=value)来给程序自动添加版本信息。https://golang.org/cmd/link/编译命令
因为 date 和 go version 的输出有空格,所以
main.BUILD_TIME
和 main.GO_VERSION
必须使用引号括起来- w 去掉 DWARF 调试信息,得到的程序就不能用 gdb 调试了。
- s 去掉符号表,panic 时候的 stack trace 就没有任何文件名/行号信息了,这个等价于普通C/C++ 程序被 strip 的效果, -w -s 如果使用这两个将会看不见文件名、行号, 对于调试不利 gdb 看不到源码
- X 设置包中的变量值
gcflags
-N
参数代表禁止优化;
-l
参数代表禁止内联;
go 在编译目标程序的时候会嵌入运行时( runtime)的二进制,禁止优化和内联可以让运行时(runtime)中的函数变得更容易调试。
6.2 实战 2
其中,build 下的命令就是构建程序的命令。在这段命令中,
LDFLAGS
为编译时的一些选项,我们在编译时注入了程序的版本号、分支、构建时间、git commit 号等信息。这些信息会注入到 main.go 中的全局变量中。在 main.go 中,我们还要进行一些配套的处理,用来打印一些程序的版本信息。如下所示。当我们执行 make build 构建可运行程序,并传递 -version 运行参数时,就会打印出程序的版本信息了:
同时在 Makefile 中,
BUILD_FLAGS
表示构建可执行文件的参数。当我们设置环境变量 gorace=1
,go build 会将 race 工具编译到程序中。最后我们会看到完整的构建命令: