golang项目结构最佳实践与讨论插件库wangyoucao577/go-project-layout的使用
Go项目结构最佳实践与wangyoucao577/go-project-layout插件库讨论
Go项目布局
这是我对于如何构建Go项目结构的理解。最重要的部分是如何将Go代码组织成包。
动机
一个好的项目结构将使你的源代码易于理解、易于测试和易于维护。Kat Zien对此有一个很好的总结,我直接引用如下:
问题与决策
- 我应该把所有内容都放在main包中吗?
- 我应该从一个包开始,然后随着时间的推移提取其他包吗?
- 如何决定某些东西是否应该放在自己的包中?
- 我应该直接使用框架吗?
- Go的编程范式是什么?
- 微服务还是单体架构?
- 包之间应该共享多少内容?
为什么我们应该关心?
因为如果Go将成为公司长期投资的语言,那么Go程序的维护和修改的便利性将是他们决策的关键因素。 - Dave Cheney, Golang UK 2016 keynote
良好结构的目标
- 一致
- 易于理解、导航和推理(“有意义”),易于更改,松耦合
- 易于测试
- “尽可能简单,但不要过于简单”
- 设计准确反映软件的工作方式
- 结构准确反映设计
演示
# shell 1
$ go version
go version go1.13.1 linux/amd64
$ cd $GOPATH
# 应用1: 反向回显
$ go get -u github.com/wangyoucao577/go-project-layout/cmd/echor
$ ./bin/echor "hello world"
dlrow olleh
# 应用2: 简单的诊断服务
$ go get -u github.com/wangyoucao577/go-project-layout/cmd/diagnosis
$ ./bin/diagnosis -alsologtostderr
I1007 18:50:05.550952 3769 main.go:33] Listen on :8000
# shell 2
$ curl "http://localhost:8000/diagnosis?diagnosis=ping"
{
"Hostname":"server",
"IP Addresses":[
"192.168.29.201/24",
"fe80::1c20:479:9094:4327/64",
"192.168.141.1/24",
"fe80::8002:2d87:c4f3:4aab/64",
"192.168.128.1/24",
"fe80::eca9:cfa0:9443:b8ff/64",
"192.168.44.209/28",
"fe80::8551:306e:7e7:6faf/64"
],
"CPUs":8,
"Remote Endpoint":"127.0.0.1:64717"
}
注意事项
这个主题属于最佳实践范畴。我想讨论其中的一些内容以及我的观点,未来可能会深入探讨更多最佳实践。
顶级目录/cmd
[我的观点:强烈推荐]
当需要多个应用程序二进制文件时,cmd
布局模式非常有用。
- 每个二进制文件获得一个子目录(例如
your_project/cmd/your_app
)- 子目录是应用程序的
main
包 - 不要在
main
包中放入大量代码。它应该只用于初始化和将所有内容连接在一起
- 子目录是应用程序的
- 这种模式也有助于保持项目/包可go get
- 意味着你可以使用
go get
命令获取(和安装)你的项目、其应用程序和库
- 意味着你可以使用
- 官方Go工具是
cmd
布局模式的一个例子。许多其他知名项目也使用相同的模式:Kubernetes、Docker、Prometheus、Influxdb
顶级目录/pkg
[我的观点:不够好,如果对你有用可以使用]
- 人们还建议在Go项目中设置一个
/pkg
顶级目录来放置公共库- 这些库可以被你的应用程序内部使用
- 也可以被外部项目使用。这是你和其他外部代码用户之间的非正式合同
- 但我认为这不是一个足够好的想法,因为:
- 它带来了混淆,因为Go工作区有一个同名的目录,目的不同
- "公共库"的含义不够明确
- 即使许多知名项目使用这种模式(Kubernetes、Docker、Grafana、Influxdb、Etcd),Docker和Etcd都再次注释了"公共库"的含义。两者都将其限制为实用程序包,这些实用程序包未来可能会移出到自己的仓库中
按依赖关系分组包
[我的观点:强烈推荐]
- 领域类型
- 领域类型是建模你的业务功能和对象的类型
- Ben Johnson - Standard Package Layout建议将它们放入根包中。这个包只包含简单的数据类型,因此会非常简单
- 在我看来,将它们放入根包或单独的子包都可以。最重要的是这个包应该非常简单,并且不依赖于应用程序中的任何其他包!
- 每个包应该只有一个目的
- 包名描述它们的目的
- 必要时,使用描述性的父包和几个实现功能的子包
- 就像encoding包
main
包连接依赖关系
更多最佳实践
- 遵循约定
- 与标准库和其他流行包相似
- 不要让人惊讶
- 明显而不是聪明
- 使用Go模块
- 避免全局范围和
init()
示例项目结构
以下是一个基于wangyoucao577/go-project-layout的示例项目结构:
my-go-project/
├── cmd/
│ ├── app1/ # 第一个应用程序
│ │ └── main.go
│ └── app2/ # 第二个应用程序
│ └── main.go
├── internal/ # 私有应用程序和库代码
│ ├── pkg1/ # 私有包1
│ └── pkg2/ # 私有包2
├── pkg/ # 公共库代码(可选)
│ ├── lib1/ # 公共库1
│ └── lib2/ # 公共库2
├── api/ # API定义
├── configs/ # 配置文件
├── scripts/ # 脚本
├── build/ # 打包和持续集成
├── deployments/ # 部署配置
├── test/ # 测试
├── docs/ # 文档
├── tools/ # 支持工具
├── go.mod # Go模块定义
└── README.md # 项目说明
示例代码
以下是一个简单的示例,展示如何使用wangyoucao577/go-project-layout中的结构:
// cmd/echor/main.go
package main
import (
"fmt"
"os"
"strings"
"github.com/wangyoucao577/go-project-layout/internal/stringutil"
)
func main() {
if len(os.Args) < 2 {
fmt.Println("Usage: echor <string>")
os.Exit(1)
}
input := strings.Join(os.Args[1:], " ")
reversed := stringutil.Reverse(input)
fmt.Println(reversed)
}
// internal/stringutil/stringutil.go
package stringutil
// Reverse returns its argument string reversed rune-wise left to right.
func Reverse(s string) string {
r := []rune(s)
for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
r[i], r[j] = r[j], r[i]
}
return string(r)
}
这个示例展示了如何组织一个简单的Go项目,其中:
cmd/echor
包含应用程序入口internal/stringutil
包含可重用的内部工具函数- 主程序只负责参数处理和结果输出,业务逻辑放在单独的包中
结论
Go项目结构的最佳实践应该关注:
- 清晰的组织和分离关注点
- 可维护性和可扩展性
- 易于测试
- 遵循社区约定
wangyoucao577/go-project-layout提供了一个实用的参考实现,展示了如何组织包含多个应用程序的Go项目。虽然它不是官方标准,但借鉴了Kubernetes、Docker等大型项目的经验,可以作为你项目结构的良好起点。
更多关于golang项目结构最佳实践与讨论插件库wangyoucao577/go-project-layout的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于golang项目结构最佳实践与讨论插件库wangyoucao577/go-project-layout的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
Golang项目结构最佳实践与go-project-layout插件库
Golang项目结构最佳实践
在Go语言中,虽然没有官方强制规定的项目结构,但社区形成了一些最佳实践:
标准项目结构
/myproject
├── cmd/ # 主应用程序目录
│ └── myapp/ # 每个可执行文件一个子目录
│ └── main.go # 主入口文件
├── internal/ # 私有应用程序和库代码
│ ├── app/ # 应用程序逻辑
│ └── pkg/ # 内部共享包
├── pkg/ # 可被外部应用导入的库代码
│ ├── util/ # 实用工具
│ └── models/ # 数据模型
├── api/ # API定义文件(Swagger, protobuf等)
├── configs/ # 配置文件
├── scripts/ # 用于执行各种构建、安装、分析等操作的脚本
├── build/ # 打包和持续集成
├── deployments/ # IaaS、PaaS、系统和容器编排部署配置
├── test/ # 额外的外部测试应用和测试数据
├── vendor/ # 依赖管理(Go Modules启用后可选)
├── go.mod # Go模块定义
└── README.md # 项目说明文档
关键原则
- 按功能而非类型组织代码:将相关的功能组织在一起,而不是按类型(如controllers, models等)
- 最小化可公开API:将大部分代码放在
internal
目录下,只有明确需要公开的部分放在pkg
- 清晰的入口点:每个可执行文件在
cmd
下有独立的子目录 - 避免循环依赖:通过合理的包划分来避免
wangyoucao577/go-project-layout插件库
go-project-layout是一个提供标准Go项目结构模板的工具,它可以帮助开发者快速初始化符合社区最佳实践的项目结构。
安装
go install github.com/wangyoucao577/go-project-layout@latest
使用示例
package main
import (
"fmt"
"os"
"path/filepath"
"github.com/wangyoucao577/go-project-layout/cmd"
)
func main() {
// 初始化一个新项目
projectPath := filepath.Join(os.TempDir(), "my-new-project")
err := cmd.InitProject(projectPath, "github.com/username/my-new-project")
if err != nil {
fmt.Printf("Failed to init project: %v\n", err)
return
}
fmt.Printf("Project initialized at: %s\n", projectPath)
}
主要功能
-
标准目录结构生成:
- 自动创建
cmd
,internal
,pkg
等标准目录 - 可选的目录结构模板(基础、Web应用、CLI工具等)
- 自动创建
-
构建工具集成:
- 生成Makefile模板
- Dockerfile模板
- CI/CD配置文件
-
文档模板:
- README.md模板
- CONTRIBUTING.md模板
- LICENSE文件
-
代码生成:
- 主程序入口模板
- 配置加载模板
- 日志初始化模板
自定义配置
可以通过.goprojectlayout.yaml
文件自定义项目结构:
# .goprojectlayout.yaml
project_name: "my-awesome-project"
module_path: "github.com/username/my-awesome-project"
structure:
- cmd
- internal
- app
- pkg
- pkg
- util
- models
- api
- configs
- scripts
skip_files:
- Dockerfile
- .github
license: "MIT"
实际应用建议
- 大型项目:使用完整结构,严格分离内部和外部代码
- 小型工具:可以简化结构,只保留
cmd
和必要的internal
或pkg
- 微服务:每个服务独立项目,使用精简结构
- 库开发:重点在
pkg
目录,可能不需要cmd
替代方案比较
- 标准库布局:更简单但不提供工具支持
- golang-standards/project-layout:更全面的模板但无自动化工具
- go-project-layout优势:
- 提供命令行工具自动化创建
- 支持自定义模板
- 集成更多开箱即用的配置
总结
合理的项目结构对Go项目的可维护性和可扩展性至关重要。wangyoucao577/go-project-layout提供了一种便捷的方式来初始化和维护符合社区最佳实践的项目结构,特别适合中大型项目或团队协作场景。对于小型项目,可以根据需要简化结构,但保持核心原则(如清晰的入口点、良好的包边界等)仍然很重要。