Go的package天然适合模块化,因其以首字母大小写控制可见性,每个包作为最小可复用单元,仅导出语义清晰的接口(如User结构体、Create函数),强制高内聚、低耦合,并通过domain层抽象与依赖注入实现层级解耦。

模块化不是为了拆包而拆包,而是让每个代码单元只做一件事、只对一件事负责,且能被独立测试和替换。
为什么 Go 的 package 天然适合模块化
Go 没有类、没有继承、没有 public/private 关键字,靠的是首字母大小写控制可见性——这本身就是一种极简的模块边界机制。一个 package 就是一个最小可复用单元,它对外暴露什么,全由导出标识符(大写开头)决定,其余全是私有实现。
-
package user只该暴露User结构体、Create()和FindByID()这类语义清晰的函数,不暴露数据库连接或 SQL 字符串 - 如果某个
internal/order包需要调用用户逻辑,它不能直接 importinternal/user,而必须依赖domain.User和user.Service接口——这是强制解耦 - 误把
db.UserModel当作业务模型返回,会导致 handler 层被迫处理字段映射、忽略字段、兼容旧版 API 等一堆脏活
go mod init 后第一件事不是写代码,是画依赖图
新建模块后,别急着写 main.go,先在纸上或白板上画三类包之间的箭头关系:哪些包可以引用 domain?哪些能用 pkg?哪些绝对禁止互相 import?
-
domain/是纯结构+接口,无依赖,所有其他包都可 import 它 -
pkg/只能依赖标准库和其它pkg/,不能碰domain或internal -
internal/子包之间禁止直接 import,跨服务调用必须走 interface + 依赖注入(比如通过func NewOrderService(userSvc user.Service)) - 一旦发现
internal/user→internal/order→internal/user这种环,说明职责没收敛,得重构
service 方法签名里出现 *sqlc.UserRow 就算失败
service 层是业务规则的守门人,它的输入输出类型决定了整个系统的稳定性。返回 DB 实体或 DTO,等于把数据层细节泄漏到上层,后续加 Redis 缓存、换 ORM、改 API 字段时,所有调用方都得跟着改。
立即学习“go语言免费学习笔记(深入)”;
- 正确返回值:只用
domain.User、[]domain.Order、error - 转换动作必须收口在 adapter 层,比如
http/handler/user.go调用userSvc.GetByID()后,再用toHTTPResponse()映射为 JSON - 错误也一样:
errno.ErrUserNotFound是 domain 层错误码,不能传sql.ErrNoRows上去;handler 才决定把它转成 HTTP 404 还是 500
真正难的不是分目录,而是每次新增一个函数时,下意识问自己:它属于哪个抽象层级?它会把哪一层的细节泄露出去?这个判断比语法本身更消耗认知资源。


















