Golang实践录:工程框架实践:搭建工程模板

本文主要介绍笔者建立的一个实用的工程框架。

背景

笔者用 Golang 开发的工程较多,但剥开业务看本质,场景类别并不多:无非是提供 Web 服务、读写数据库、跑一些定时的或一次性的任务。如果每个工程都从零搭建,重复劳动不说,风格还会漂移------今天这个工程日志打终端、明天那个工程又写文件,虽然现在还没有将自己工程移交他人的先例,但自己这么折腾自己,也不是办法。

于是笔者把这些年一个人沉淀下来的代码、习惯固化成一套工程模板:目录结构、配置读取、编译脚本等,都有相同的约定。如果要做新的工程,复制模板改业务即可,框架层基本不动。

本文关注模板的目录结构和功能以及从模板创建新工程的步骤。

设计

目录总览

模板的顶层结构如下(工程名以 foo 代指):

text 复制代码
foo/
├── 3rdpart/              # 第三方 C 库,按平台分目录归档
├── cmd/                  # 应用/模块入口:server、client、misc... 及根命令 root.go
├── common/               # 同包多文件的辅助代码(文件不多时也可直接放根目录)
├── config/               # yaml 配置文件(config.yaml / config_env.yaml / client.yaml)
├── data/                 # 静态资源与前端页面(html/css/js/img)
├── internal/             # 内部业务实现,仅本工程可 import
│   ├── conf/             # 配置结构体、加载与热更新
│   ├── server/           # 服务端内部支撑
│   └── client/           # 客户端内部实现
├── log/                  # 运行日志目录(运行时生成,按程序名/主机名再分子目录)
├── pkg/                  # 通用工具库(日志、加密、数据库封装等,可跨工程复用)
├── vendor/               # go mod vendor 生成的第三方依赖
├── main.go               # 程序入口,只调用根命令
├── mybuild.sh            # 编译脚本
├── run.sh                # 运行脚本(开发用)
├── VERSION               # 版本号文件,编译时读取并注入二进制
├── go.mod / go.sum
└── readme.md             # 工程说明

各目录职责

目录/文件 职责 备注
cmd/ 各"角色"的入口与根命令 服务端、客户端、杂项测试都在此,由根命令统一调度
internal/ 业务实现 Go 编译器强制外部不可引用,天然划分边界
pkg/ 通用工具库 与业务无关,理论上可被其它工程复用
config/ 配置文件 区分开发/生产环境,程序自动回退
data/、db/ 静态资源与本地数据 与代码分离,便于打包发布
log/ 运行日志 运行时生成,不入版本库
3rdpart/ 第三方 C 库 仅供 cgo 组件使用,纯 Go 工程可去掉
VERSION 版本号 编译脚本读取后 -ldflags 注入
main.go 入口 只做一件事:执行根命令

两条设计约束

  • 尽量 pure Go。工程内凡是可选实现的依赖都优先选纯 Go 版本,例如 sqlite 驱动用纯 Go 实现而非依赖 cgo 的版本。这样组件默认可以跨平台编译,只有确有必要(如连接oracle使用的第三方库)才引入 cgo,且把它隔离在特定模块内。
  • 编译、配置、运行约定一致 。统一由 mybuild.sh 负责编译、VERSION 管版本、config/ 管配置。

实践

从零创建

若从头开始,先初始化模块并拉取依赖:

sh 复制代码
go mod init foo
go mod tidy
go mod vendor

go mod vendor 值得养成习惯:第三方依赖入库,离线可编、版本可控,编译时加 -mod vendor 即可。

由模板改造

复制模板目录后,只需三步:

  1. 改模块名 :把 go.mod 的 module 名及代码内 import 路径中的 foo 批量替换为新工程名;
  2. 删无关模块 :用不到的 cmd/client、3rdpart 等整目录删除,多余的第三方依赖 go mod tidy 自动清理;
  3. 改配置与元信息 :更新 config/ 下的 yaml、VERSION、readme.md。

框架层的加载、编译、日志无需改动。

编译与运行

模板在根目录提供统一入口,任何平台都执行同一命令:

sh 复制代码
sh mybuild.sh            # 编当前平台
sh mybuild.sh linux arm  # 指定平台,如果不涉及cgo,可以在Windows上执行

实例:

复制代码
$ sh mybuild.sh
build for platform: windows amd64  version: 0.0.1  output file: foobar-win.exe

$ sh mybuild.sh linux
cross build under windows
build for platform: linux amd64  version: 0.0.1  output file: foobar

$ sh mybuild.sh linux arm
not support arm, use arm64
cross build under windows
build for platform: linux arm64  version: 0.0.1  output file: foobar-arm

通过名称能快速辨别出其运行的目标机器平台。

开发期则用 run.sh 完成"编译 + 运行",免去手工切来切去。日志统一落到 log/ 下,按 程序名_主机名 分子目录,多实例部署互不干扰。

值得说明的是,本工程设置的config目录存放配置文件,能区分开发测试环境和生产环境(由代码自动检查判断config.yaml 和 config_env.yaml)。

技术栈

这里记录一些工程中使用的技术线,如下:

  • 命令行参数:使用github.com/spf13/cobra实现命令行参数读取,大大扩大程序功能范围。通过参数,可在同一工程既实现业务功能,又可支持测试程序。
  • 参数配置:使用github.com/spf13/viper和github.com/fsnotify/fsnotify支持yaml格式参数的加载和热更新,减少参数修改导致重启程序次数。
  • 数据库:使用github.com/jmoiron/sqlx提升查询语句开发效率(注:不使用xorm相关库)。需要说明的是,对于常用的sqlite数据库类型,本工程使用modernc.org/sqlite,而不是github.com/mattn/go-sqlite3,因为前者为pure go实现,可跨平台编译使用,就功能而言未发现其与后者有较明显差异,因此使用之。
  • web功能:使用github.com/gin-gonic/gin实现web接口,使用内嵌的html模板实现前端页面(注:暂不使用vue等流行框架)。实现系统信息查询、版本号查询等常用接口。

小结

上述框架目录结构清晰,分工明确,具备基础功能,比如:目录职责明确、配置与代码分离、构建信息统一注入、入口统一调度。当然模板不是一成不变的,由于笔者不断使用和更新,随着实践的深入,功能也将变得丰富,后续根据实际情况,会抽取一些功能点单独作为文章。

相关推荐
Wx-bishekaifayuan13 小时前
springboot甘肃特产服务平台50301-计算机课程设计、毕业设计
spring boot·后端·python·django·课程设计·express·旅游
Thneonl13 小时前
服务不通先别抓包:DNS 解析器的坑比内核网络栈多
后端·程序员
Thneonl13 小时前
一启动就 Exited(137):内存限制背后藏了四个参数
后端·架构
斯维赤13 小时前
从 @Tool 到 Agent 流水线:LangChain4j 进阶教学,一个库打全套
java·后端
对象存储与RustFS13 小时前
RustFS 生产部署实战:Docker / 二进制 / K8s Operator 三条路径怎么选
后端
知守观13 小时前
Spring Boot 2.1.0 停维护5年,10个依赖4个有CVE——2022年老项目的安全体检报告
spring boot·后端
zhanghaha131413 小时前
Python进阶教程:35_PyQt (保姆级零基础教程)
开发语言·python·pyqt
SimonKing13 小时前
SSE项目`nexus-sse`持续优化,不一样的视觉效果
java·后端·程序员
斑鸠喳喳13 小时前
读写锁模式 Read-Write Lock
java·后端
Thneonl13 小时前
etcd 磁盘写满的那 6 分钟:控制面是怎么一步步瘫的
后端·架构