微服务由边界、部署单元和数据所有权定义,目录结构仅显性化该边界;cmd/下每个微服务应有独立子目录(如cmd/order-svc),main.go仅加载配置、初始化依赖、调用app.Run(),构建命令须分离以支持按服务发布。

微服务不是靠目录结构定义的,而是靠边界、部署单元和数据所有权。目录结构只是把这种边界显性化——选错路径,反而会掩盖真正的服务拆分问题。
cmd/ 下每个服务一个子目录,别堆一起
一个微服务对应一个可执行二进制,就该有一个独立的 cmd/ 子目录。比如订单服务叫 order-svc,那就建 cmd/order-svc/main.go;用户服务叫 user-svc,就建 cmd/user-svc/main.go。
- 错误做法:所有服务共用
cmd/main.go,靠 flag 区分启动哪个——这根本不是微服务,是单体伪装 - 正确做法:
main.go只做三件事:加载配置、初始化 logger/db/redis 等依赖、调用app.Run() - 构建命令也得分开:
go build -o bin/order-svc cmd/order-svc,CI/CD 才能真正按服务发布 - 如果项目初期只有一个服务,
cmd/不是必须项——直接放根目录main.go更干净,别硬套模板
internal/ 按领域组织,不是按技术层切片
别一上来就建 internal/handler、internal/service、internal/repository 这种“三层架构”目录。Go 不需要这种仪式感,尤其在微服务里,它容易让领域逻辑被技术术语割裂。
- 推荐方式:以业务实体为中心,比如
internal/order下直接放order.go(domain struct)、handler.go、service.go、repo.go—— 全部围绕“订单”这个概念聚拢 - 好处是:改订单逻辑时,所有相关代码都在同一目录,
go test ./internal/order就能覆盖全部 - 陷阱:把
model.User放internal/user/model.go,又在internal/order里重复定义Order.User字段——应该统一用internal/user提供的类型,或通过接口解耦 -
internal/的本质是「本项目私有」,不是「代码保险柜」——别把能复用的通用逻辑(如重试 HTTP client)塞进来,该去pkg/就去pkg/
pkg/ 要经得起外部引用,否则不如删掉
pkg/ 不是放“暂时没想好放哪”的代码的地方。它的存在前提是:有人真在别的项目里 import 它,而且你愿意维护它的 API 兼容性。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
立即学习“go语言免费学习笔记(深入)”;
- 判断标准:如果你写的
pkg/metrics还没被第二个服务引用过,半年内也没人提 issue 或 PR,那它大概率该回迁到internal/metrics - 命名要稳定:
pkg/httpx比pkg/httpclient更合适,因为后者暗示“这只是个 client”,而前者表明它是一套扩展标准库的协议工具 - 必须带文档注释:
// Package httpx provides HTTP utilities with retry, timeout, and tracing support.—— 没这行注释的pkg就是半成品 - 测试要完整:
pkg/uuidx必须有覆盖率报告,且go test -v ./pkg/uuidx能独立跑通,不依赖internal/任何东西
config/ 和 migrations/ 别混进业务包
配置加载和数据库迁移是基础设施行为,它们不该和业务逻辑耦合。哪怕只有一份 config.yaml,也要单独抽成 config/ 目录。
-
config/里只放解析逻辑:读环境变量、校验字段、绑定结构体——不放业务常量(如MaxRetries = 3),那些属于internal/order的内部策略 -
migrations/必须独立于internal/:golang-migrate 的.sql文件不能放在internal/db/migration里,否则 CI 构建镜像时可能漏掉 - 常见坑:
internal/order/repo.go里硬编码"host=localhost"—— 配置必须从config.Load()注入,否则无法在 k8s 里切换 staging/prod - 路径敏感点:
go run时工作目录影响配置文件读取,建议在cmd/xxx/main.go开头加os.Chdir(filepath.Dir(os.Args[0]))或统一用绝对路径加载
真正卡住团队的,从来不是目录名写错,而是把“订单取消”这种业务动作,分散在 internal/handler、pkg/eventbus、internal/notify 三个地方修改——目录结构救不了设计失焦,它只放大问题。

















