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/client3rdpart 等整目录删除,多余的第三方依赖 go mod tidy 自动清理;
  3. 改配置与元信息 :更新 config/ 下的 yaml、VERSIONreadme.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/vipergithub.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等流行框架)。实现系统信息查询、版本号查询等常用接口。

小结

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

相关推荐
纪伊路上盛名在1 小时前
Kabsch算法的Julia实现
开发语言·算法·julia·序列分析·蛋白质·rmsd
玖玥拾1 小时前
Lua 基础语法(三) 元表 metatable、元方法、模拟面向对象
开发语言·unity·lua
Lynko1 小时前
C语言指针高阶
后端
the局外人1 小时前
学习 FastAPI 的 Day 2:用异步 ORM 完成增删改查
后端·python·fastapi
维天说1 小时前
Agent会听人话,反而更难管
java·开发语言·人工智能
SamDeepThinking1 小时前
if嵌套最好控制在3层以内
java·后端·程序员
苏渡苇1 小时前
Spring Insight 里上报 Span 为什么用 JDK HttpClient 而不是 RestTemplate
java·后端·spring·spring cloud·springboot·性能监控
tonydf1 小时前
AI驱动历史项目安全审查
后端·ai编程
会飞的拖把1 小时前
Python 面向对象编程详解:从类与对象到动态属性方法
开发语言·python