17 KiB
Go Monorepo 目录结构设计
适配小团队开发,满足业务独立部署、发布互不影响,基于docker compose实现一键发布,遵循轻量设计、无过度封装原则,整体结构兼顾可维护性和开发效率,各业务模块解耦且资源可复用。
核心设计思路
- 单仓库多业务:所有代码归拢在一个monorepo,团队协作更便捷;
- 业务模块独立:每个业务单独放在
services/下,拥有独立的Go代码、配置、Dockerfile、docker-compose子配置,做到独立构建/部署/发布; - 公共资源复用:抽离公共库、工具、中间件配置,避免重复开发,公共代码变更需做兼容性保证(不影响各业务);
- 根目录一键部署:通过根目录
docker-compose.yml聚合所有业务,支持全局启动或单独启动某一个业务,满足团队开发/生产不同场景。
最终目录结构
go-monorepo/
├── Makefile # 全局快捷命令(构建、启动、停止、日志等,简化团队操作)
├── docker-compose.yml # 根级compose,聚合所有业务,支持独立启动单个服务
├── .env # 全局环境变量(数据库、Redis等公共中间件地址,团队统一配置)
├── .gitignore # git忽略规则(go编译产物、docker日志、.env等)
├── go.work # Go 1.18+ 工作区文件,管理多模块,实现monorepo依赖管理
├── go.work.sum # 工作区依赖校验文件
├── common/ # 公共复用模块(所有业务均可依赖,团队统一维护,保证通用性)
│ ├── go.mod # 公共模块的mod文件(模块名:go-monorepo/common)
│ ├── go.sum
│ ├── db/ # 公共数据库封装(PostgresSQL连接、基础CRUD,轻量无过度封装)
│ │ ├── postgres.go
│ │ └── options.go
│ ├── redis/ # 公共Redis封装(连接池、基础操作)
│ │ └── redis.go
│ ├── logger/ # 公共日志(zap轻量封装,统一日志格式)
│ │ └── logger.go
│ ├── utils/ # 公共工具函数(加密、时间、字符串等)
│ │ └── common.go
│ └── middleware/ # 公共中间件(HTTP跨域、限流、日志,Gin适配)
│ └── cors.go
├── services/ # 业务服务根目录(每个子目录是一个独立业务,可单独部署)
│ ├── user/ # 业务1:用户服务(示例,团队可按业务拆分,如order/pay/user)
│ │ ├── go.mod # 独立mod文件(依赖根目录common,模块名:go-monorepo/services/user)
│ │ ├── go.sum
│ │ ├── main.go # 服务入口(Gin/GRPC均可,示例用Gin)
│ │ ├── conf/ # 业务独立配置(可覆盖全局.env,支持多环境)
│ │ │ └── config.go
│ │ ├── api/ # 接口层(HTTP路由、请求响应体)
│ │ │ └── user_api.go
│ │ ├── service/ # 业务逻辑层
│ │ │ └── user_service.go
│ │ ├── dao/ # 数据访问层(操作PostgresSQL,依赖common/db)
│ │ │ └── user_dao.go
│ │ ├── model/ # 数据模型(数据库表结构、结构体)
│ │ │ └── user_model.go
│ │ ├── Dockerfile # 业务独立Dockerfile(单独构建镜像,不影响其他业务)
│ │ └── docker-compose.yml # 业务独立compose(单独部署时使用,仅包含自身+依赖的中间件)
│ ├── order/ # 业务2:订单服务(和user服务结构完全一致,独立部署)
│ │ ├── [结构同user服务]
│ └── pay/ # 业务3:支付服务(团队可一人负责一个核心业务,完美适配)
│ ├── [结构同user服务]
├── deploy/ # 部署相关资源(非必须,可放公共配置、初始化脚本)
│ ├── postgres/ # PostgresSQL初始化(建库、建表、初始化数据脚本)
│ │ └── init.sql
│ └── redis/ # Redis初始化(可选,配置文件、持久化脚本)
│ └── redis.conf
└── logs/ # 全局日志目录(docker挂载,所有服务日志统一存放,便于排查)
└── .gitkeep
关键文件说明(核心实现,保证独立部署+复用)
1. Go工作区文件 go.work(monorepo核心)
Go 1.18+ 引入的工作区模式是实现monorepo的关键,无需将common发布到私服,本地即可实现多模块依赖,且每个业务模块独立管理自身依赖。
go.work 内容:
go 1.21 // 建议使用稳定版Go,团队统一版本
// 加入公共模块
use ./common
// 加入所有业务服务模块(新增业务时只需追加一行)
use ./services/user
use ./services/order
use ./services/pay
作用:所有模块在同一个工作区,业务服务可直接通过import "go-monorepo/common/xxx"依赖公共库,本地开发无需手动go mod replace,团队协作无依赖问题。
2. 公共模块 common/go.mod(轻量封装,无过度设计)
仅声明公共依赖,暴露简单接口,不做复杂业务封装,示例:
module go-monorepo/common
go 1.21
require (
github.com/gin-gonic/gin v1.9.1
github.com/jmoiron/sqlx v1.3.5
github.com/redis/go-redis/v9 v9.3.0
go.uber.org/zap v1.26.0
github.com/lib/pq v1.10.9 // PostgresSQL驱动
)
require (
// 间接依赖(go mod tidy 自动生成)
)
3. 业务模块 services/user/go.mod(独立依赖,发布不影响其他业务)
业务模块仅依赖自身需要的包+公共模块,每个业务的mod独立管理,升级依赖/修改代码不会影响其他业务,示例:
module go-monorepo/services/user
go 1.21
// 依赖本地公共模块(工作区模式下,无需指定版本)
require go-monorepo/common v0.0.0-00010101000000-000000000000
// 业务自身的依赖(仅引入当前业务需要的,按需添加)
require (
github.com/gin-gonic/gin v1.9.1
github.com/joho/godotenv v1.5.1 // 环境变量解析
)
// 工作区模式下的本地依赖映射(go work sync 自动生成,无需手动修改)
replace go-monorepo/common => ../../common
关键:新增/升级业务依赖仅修改当前业务的go.mod,其他业务不受影响,实现发布隔离。
4. 业务独立Dockerfile(services/user/Dockerfile)
轻量多阶段构建,仅构建当前业务,镜像体积小,独立构建不影响其他业务,所有业务的Dockerfile格式统一(团队规范),示例:
# 构建阶段
FROM golang:1.21-alpine AS builder
WORKDIR /app
# 复制当前业务的代码(仅复制user目录,不涉及其他业务)
COPY . .
# 设置Go代理,加速构建
ENV GOPROXY=https://goproxy.cn,direct
# 编译(静态编译,无系统依赖)
RUN go build -ldflags="-s -w" -o user-server main.go
# 运行阶段(使用alpine基础镜,体积仅数MB)
FROM alpine:3.19
WORKDIR /app
# 从构建阶段复制编译产物
COPY --from=builder /app/user-server .
# 复制业务配置(可选)
COPY --from=builder /app/conf ./conf
# 暴露业务端口(user服务示例用8080,order可8081,pay可8082,互不冲突)
EXPOSE 8080
# 启动命令
CMD ["./user-server"]
特点:每个业务单独构建镜像,镜像名区分(如go-monorepo-user:latest),发布时仅推送当前业务的镜像即可。
5. 根目录docker-compose.yml(聚合所有业务,支持独立启动)
核心满足全局启动所有服务/单独启动某一个业务,所有中间件(PostgresSQL/Redis)全局共享(团队无需为每个业务单独部署中间件),通过depends_on控制依赖关系,业务服务之间通过服务名访问(如user服务访问order服务:http://order:8081)。
核心内容:
version: '3.8' # 兼容主流Docker版本
services:
# 公共中间件:PostgresSQL(所有业务共享,团队统一管理)
postgres:
image: postgres:16-alpine
container_name: monorepo-postgres
environment:
POSTGRES_USER: ${PG_USER:-root}
POSTGRES_PASSWORD: ${PG_PWD:-123456}
POSTGRES_DB: ${PG_DB:-monorepo}
ports:
- "${PG_PORT:-5432}:5432"
volumes:
- ./deploy/postgres/init.sql:/docker-entrypoint-initdb.d/init.sql # 初始化脚本
- pg_data:/var/lib/postgresql/data # 数据持久化
networks:
- monorepo-net
restart: always
# 公共中间件:Redis(所有业务共享)
redis:
image: redis:7-alpine
container_name: monorepo-redis
ports:
- "${REDIS_PORT:-6379}:6379"
volumes:
- ./deploy/redis/redis.conf:/etc/redis/redis.conf
- redis_data:/data
command: redis-server /etc/redis/redis.conf
networks:
- monorepo-net
restart: always
# 业务服务1:用户服务
user:
build:
context: ./services/user # 仅构建user目录,独立构建
dockerfile: Dockerfile
container_name: monorepo-user
environment:
- GIN_MODE=release
- PG_ADDR=postgres:5432 # 访问公共PG,通过服务名
- REDIS_ADDR=redis:6379 # 访问公共Redis
ports:
- "8080:8080"
volumes:
- ./logs/user:/app/logs # 日志挂载到本地
networks:
- monorepo-net
restart: always
depends_on:
- postgres
- redis
# 可选:限制资源(团队开发/测试环境,避免单个服务占满资源)
deploy:
resources:
limits:
cpus: "0.5"
memory: "512M"
# 业务服务2:订单服务(和user服务结构一致,独立部署)
order:
build:
context: ./services/order
dockerfile: Dockerfile
container_name: monorepo-order
environment:
- GIN_MODE=release
- PG_ADDR=postgres:5432
- REDIS_ADDR=redis:6379
ports:
- "8081:8081"
volumes:
- ./logs/order:/app/logs
networks:
- monorepo-net
restart: always
depends_on:
- postgres
- redis
deploy:
resources:
limits:
cpus: "0.5"
memory: "512M"
# 业务服务3:支付服务(独立部署)
pay:
build:
context: ./services/pay
dockerfile: Dockerfile
container_name: monorepo-pay
environment:
- GIN_MODE=release
- PG_ADDR=postgres:5432
- REDIS_ADDR=redis:6379
ports:
- "8082:8082"
volumes:
- ./logs/pay:/app/logs
networks:
- monorepo-net
restart: always
depends_on:
- postgres
- redis
deploy:
resources:
limits:
cpus: "0.5"
memory: "512M"
# 全局网络:所有服务在同一个网络,通过服务名互通
networks:
monorepo-net:
driver: bridge
# 全局数据卷:中间件数据持久化,不会随容器删除
volumes:
pg_data:
redis_data:
6. 业务独立docker-compose.yml(services/user/docker-compose.yml)
满足单独部署某一个业务的场景(如仅修改了user服务,只需发布user,无需启动其他业务),仅包含当前业务+必要的中间件(也可连接外部已有的中间件),示例:
version: '3.8'
services:
user:
build:
context: .
dockerfile: Dockerfile
container_name: standalone-user
environment:
- GIN_MODE=release
- PG_ADDR=192.168.1.100:5432 # 可连接外部PG/Redis,无需启动本地中间件
- REDIS_ADDR=192.168.1.100:6379
ports:
- "8080:8080"
volumes:
- ../../logs/user:/app/logs
restart: always
networks:
default:
driver: bridge
7. 全局Makefile(简化团队操作,避免记复杂命令)
团队统一使用Makefile命令,降低操作成本,所有命令支持全局/单独业务执行,示例:
# 全局启动所有服务(中间件+所有业务)
up:
docker compose up -d
# 单独启动某一个业务(如user,make up-svc svc=user)
up-svc:
docker compose up -d $(svc)
# 全局停止所有服务
down:
docker compose down
# 单独停止某一个业务
down-svc:
docker compose stop $(svc)
# 构建所有业务镜像
build:
docker compose build
# 单独构建某一个业务镜像(核心:发布时仅构建当前业务)
build-svc:
docker compose build $(svc)
# 查看所有服务日志
logs:
docker compose logs -f
# 查看某一个业务日志
logs-svc:
docker compose logs -f $(svc)
# 重新启动某一个业务(修改代码后,一键重启)
restart-svc:
docker compose restart $(svc)
# 清理无用镜像/容器(团队开发环境,定期清理)
clean:
docker system prune -f
# 初始化Go工作区(新成员拉取代码后,一键执行)
go-init:
go work init
go work use ./common
go work use ./services/user
go work use ./services/order
go work use ./services/pay
go mod tidy
团队使用示例:
- 新成员拉取代码:
make go-init(初始化Go工作区,解决依赖) - 开发用户服务:
make build-svc svc=user && make up-svc svc=user(仅构建+启动user服务) - 发布订单服务:
make build-svc svc=order && make restart-svc svc=order(仅构建+重启order服务,不影响user/pay) - 全局启动所有服务(测试环境):
make up
团队开发&发布流程(适配团队)
开发流程(一人负责一个核心业务,互不干扰)
- 拉取monorepo代码:
git clone <仓库地址> && cd go-monorepo && make go-init; - 开发各自业务(如A开发user,B开发order,C开发pay),仅修改
services/[自己的业务]目录下的代码; - 公共代码修改(如common/logger):需团队沟通,保证兼容性(不修改现有接口,仅新增),避免影响其他业务;
- 本地测试:使用
make build-svc svc=xxx && make up-svc svc=xxx仅启动自己的业务,快速验证。
发布流程(业务独立发布,不影响其他服务)
生产/测试环境发布单个业务(核心需求)
# 1. 拉取最新代码(仅拉取,不影响运行中的服务)
git pull origin main
# 2. 仅构建当前业务的镜像(如发布pay服务)
make build-svc svc=pay
# 3. 仅重启当前业务容器(秒级重启,不影响其他服务)
make restart-svc svc=pay
首次部署/全局发布(如测试环境初始化)
make go-init && make build && make up
关键满足点验证
- ✅ 无需过度封装:common仅做轻量基础封装,无业务逻辑,业务模块结构简单(api/service/dao/model四层,团队易理解);
- ✅ 业务独立部署:每个业务有独立Dockerfile、compose,支持
make up-svc svc=xxx单独启动,也可通过业务目录下的compose单独部署; - ✅ 发布互不影响:发布时仅构建/重启当前业务镜像,其他业务容器正常运行,公共代码变更做兼容性保证;
- ✅ docker compose发布:根目录compose聚合所有服务,支持全局/单独发布,Makefile封装所有命令,团队使用便捷;
- ✅ 团队适配:一人一个核心业务,目录结构清晰,公共代码统一维护,开发/发布命令简化,无复杂配置。
扩展建议(团队可按需添加,不影响现有结构)
- 新增业务:在
services/下新建业务目录,复制user服务的基础结构(go.mod、Dockerfile、目录分层),在go.work和根docker-compose.yml中追加一行,即可实现独立部署; - 多环境配置:在各业务
conf/下添加dev/ prod/ test目录,通过环境变量ENV=prod指定配置文件,根.env区分不同环境的变量; - 接口文档:在各业务
api/下添加swagger/目录,使用swag init生成接口文档,独立访问(如user服务:http://localhost:8080/swagger/index.html); - 监控告警:根
docker-compose.yml中新增Prometheus+Grafana服务,所有业务暴露metrics接口,统一监控(轻量配置,团队易维护); - 代码规范:添加
golangci-lint配置文件(.golangci.yml),团队提交代码前执行golangci-lint run,保证代码风格统一。
总结
本次设计的Go Monorepo核心围绕小团队高效开发和业务独立部署发布,通过Go工作区实现monorepo依赖管理,以services/为业务隔离边界,结合Docker和docker compose实现独立构建、独立发布、全局聚合,同时通过Makefile封装所有操作,降低团队协作成本。整体结构轻量、可扩展,无需过度设计,完全匹配任务要求。