一个Go项目的结构设计始终遵循Go语言的简洁高效理念。一个合理和良好的布局可以提高代码的可读性,简化依赖管理,并优化编译过程。
像cmd、internal和docs这样的目录是标准Go项目的基础,起着不同的作用。比如,cmd目录是可执行文件的入口,docs是所有相关文档的入口,而internal包含项目私有代码。适当的包划分可以避免循环依赖,便于单元测试和代码复用。
然而,这种Go项目布局可能导致过多的目录层级和包划分,会给管理工作带来负担,并有时让初学者感到不知所措。
因此,在设计时如何把握什么算是“合理”,就成了关键。
这篇文章,让我们尝试在目录结构和包设计中找到简洁和功能之间的平衡,使项目能够在变化中健康迭代。
标准布局
像cmd和internal这样的目录结构是由一些Go语言社区的开发者在系统总结之前进行了总结,并在标准Go项目布局中得到了进一步的概括,该项目已经获得了超过4万个star。尽管其起源是一个提示,但标准布局已经成为Go项目目录结构的事实标准。
这并不是由核心Go开发团队定义的官方标准。
my-app/ # Root directory of the project
|── cmd/ # Executables directory
└── myapp/ # Main application package
└── main.go # Main application entry point
|── internal/ # Private application and library code
└── package1/ # Internal package 1
|── pkg/ # Library code that can be exported
└── package2/ # External package 2
|── api/ # API protocol definitions directory
|── configs/ # Configuration files directory
|── deployments/ # Deployment configurations and scripts
|── scripts/ # Scripts for build, install, analysis, etc.
|── build/ # Packaging and Continuous Integration
|── tests/ # Additional external test apps and test data
|── docs/ # Design and user documents
|── tools/ # Supporting tools for the project
|── examples/ # Application or public library examples
|── third_party/ # External helper tools, forked code, and other 3rd party utilities
|── githooks/ # Git hooks
|── assets/ # Other assets like images, logos, etc.
|── vendor/ # Dependency package directory (if using vendor mode)
|── go.mod # Module dependency file
|── go.sum # Module checksum file for dependency verification
如果你经常阅读源代码,你会轻易地发现,大多数在GitHub上知名的Go开源项目基本上都遵循上述布局,比如Kubernetes这个大型Go项目。
图片
让我们简单看一下。
- 与Go模块相关的go.mod和go.sum是必不可少的。
- pkg目录包含api、apis、kubectl等包,可应用于外部项目,比如基于Kubernetes的开发。
- cmd包含了Kubernetes中各种命令行的main方法入口,比如kubectl.go。
- api目录存储与openApiv3相关的json文件。
- test目录包含所有的e2e和集成测试代码,根据不同的包进行了分别存储。
- third_party存储第三方引用的工具,比如protobuf。
- vendor用于存储外部依赖,比如k8s.io、etcd等。
- docs目录目前为空。
当然,Kubernetes项目并不完全遵循标准布局,因为其规模较大。例如,许多Kubernetes脚本存储在build和cluster目录中,而不是scripts目录。还有一些用于特定需求的目录,比如hacks和staging。
官方布局
2023年发布的文章《组织Go模块》揭示了Go团队对布局的不同观点,提供了根据项目复杂性设计目录结构的参考,涵盖了具有少量Go文件、单个cmd命令或简单包的项目,以及具有多个cmds和多个包的项目。
对它们进行总结如下,并将其作为下一节的官方布局。
my-module/ # Root directory for the module with go.mod
├── go.mod # Module's dependency file
├── go.sum # The module's checksums for dependency validation
├── cmd/ # Directory for commands (each subdirectory here is a main package)
│ └── my-app/ # Main application for this module
│ └── main.go # Main application entry point
├── internal/ # Internal packages meant for use only within this module
│ └── mylib/ # An internal package example
│ └── mylib.go # The package's specific code
├── api/ # API protocol definitions, e.g., protocol buffer files
├── web/ # Web application specific components: static files, server-side templates, SPAs, etc.
├── pkg/ # Library code that's ok to use by external applications (deprecated by some in the community)
│ └── mypkg/ # An example package that could be imported by other applications
│ └── mypkg.go # Package code
├── README.md # Project README file
├── LICENSE # License file
├── .gitignore # Specifies intentionally untracked files to ignore
└── ... <-- Other directories and files as needed
标准布局与官方布局
这两种布局有一些共同的思想。
- 模块化。不同的功能会被放入不同的包中,以提高可重用性。
- 提高可见性。根据功能将不同的包存储在不同的目录中,以提高可读性。
基于这些概念,标准布局中有一个通用的cmd目录来存储命令行代码,子包用于保存多个命令,internal目录用于保存不对外共享的代码。目录路径和包名称与main.go作为入口文件保持一致。
但是,它们对于像pkg和util这样的目录有不同的考虑,例如,Russ Cox反对以pkg和util等模糊方式命名软件库。此外,由于社区的贡献,标准布局比官方建议覆盖范围更广,添加了像scripts和build这样的目录。
Go-Clean-Template
标准布局和官方布局都是通用的,将项目分为cmd项目和非cmd项目,因此对于包含entity和dao等多个包的复杂项目(如使用Go开发的Web项目),它们并不是首选。go-clean-template则专为这类Web项目量身定制。
go-clean-template/ <-- Root directory of the project
├── cmd/ <-- Main application entry points
│ └── appname/ <-- Specific startup logic and configuration for 'appname' app
│ └── main.go <-- Main application startup entry
├── internal/ <-- Internal modules, not importable by external applications
│ ├── entity/ <-- Entities (business objects) of the application
│ ├── usecase/ <-- Use case layer containing business logic interfaces
│ ├── repository/ <-- Data storage interfaces
│ ├── handler/ <-- HTTP handlers for receiving requests and calling use cases
│ └── config/ <-- Configuration related code
├── pkg/ <-- Public library code that can be imported by other projects
├── test/ <-- External testing code
├── .dockerignore <-- Specifies files to ignore in Docker builds
├── .gitignore <-- Specifies intentionally untracked files to ignore in Git
├── Dockerfile <-- Docker configuration file for containerization
├── go.mod <-- Go module dependencies file
├── go.sum <-- Checksums for Go module dependencies
└── Makefile <-- Makefile containing automation commands
作为标准布局的扩展,go-clean-template保留了pkg目录,以便更好地管理众多公共库。它明确定义了像entity、repository、config等子包,省去了我们为它们命名的工作。它还有像test和integration-test这样的目录,用于放置相应的测试代码,提高可读性。
小结
本文带大家深入研究了组织Go代码库的三种布局:
标准布局提供了一个由社区驱动的、广泛适用的结构,非常适合需要清晰关注点分离的大型项目。
官方布局由Go的创建者认可,强调简洁和灵活性,适用于各种项目,特别是那些优先考虑模块管理的项目。
基于Clean Architecture原则的go-clean-template在需要将业务逻辑、数据访问和接口层明确分离以提高可维护性和可测试性的项目中表现出色。
这三种范式适应不同的项目需求,每种都提供了一套可自适应和组合的指南。选择其中之一取决于具体项目的要求、规模和复杂性。