domain层只放纯粹的领域模型、核心规则及接口定义,如User结构体、Activate()方法、UserRegisteredEvent事件和UserRepository接口,禁止依赖外部包或含副作用操作。

微服务项目里 domain 层该放什么
domain 层不是“业务逻辑大杂烩”,而是纯粹的领域模型与核心规则。它不能依赖 infra、handler 或任何外部包,连 log、time 这类标准库之外的导入都要警惕。
常见错误是把数据库实体(如 UserModel)和领域对象(如 User)混在一起,或在 domain 里直接调用 redis.Client。正确做法是只定义接口(如 UserRepository),具体实现交给 infra 层。
-
User结构体只含字段和方法(如Activate()),不涉及序列化、存储、HTTP 状态 - 领域事件(如
UserRegisteredEvent)定义在此,但不触发、不发布 - 所有方法必须是纯函数式或仅操作自身状态,禁止副作用
internal 目录下 handler 和 service 怎么划界
handler 只做三件事:解析请求(c.Param / c.ShouldBindJSON)、调用 service、构造响应(c.JSON)。它不该有 if 判断业务分支,也不该拼接 SQL 或组装 DTO。
service 是协调者,负责编排 domain 对象、调用多个 repository、处理事务边界。它可返回 error,但绝不返回 http.StatusXxx —— 那是 handler 的责任。
立即学习“go语言免费学习笔记(深入)”;
- handler 中不要出现
if user.Status == "inactive"这类业务判断,应交由 service 返回明确 error(如ErrUserInactive) - service 方法名建议用动词+名词,如
TransferMoney()、CancelOrder(),而非DoSomething() - 避免在 service 里 new repository 实例;通过构造函数注入,便于单元测试 mock
pkg 和 internal 的分工陷阱
pkg 放可复用、无项目上下文的通用能力,比如 pkg/trace(OpenTelemetry 封装)、pkg/validator(自定义校验规则)。它必须能被其他项目 go get 直接引用。
internal 是项目私有层,包括 internal/handler、internal/service、internal/infra —— 它们互相引用没问题,但外部模块无法 import internal/xxx,这是 Go 编译器强制的隔离机制。
- 别把
internal/infra/mysql里的NewDB()拆到pkg/db,除非你真打算开源这个 MySQL 初始化逻辑 -
pkg/util里禁止出现config.Load()或cache.Get("user:"),那已绑定项目环境 - 如果某个 “工具函数” 需要读取
app.Config,它就该在internal下,而不是pkg
go.mod 和 main.go 放哪才不踩包循环引用
微服务通常只有一个 cmd/{service-name}/main.go,它负责初始化配置、注册 infra 组件、构建 service 实例、启动 HTTP/gRPC server。所有依赖注入都在这里完成。
go.mod 必须位于项目根目录,且 module 名不能带 /cmd 或 /internal 后缀(如 github.com/your/project),否则 go list -m all 会混乱,CI 构建时 vendor 或 go install 可能失败。
- 绝对禁止在
internal/service里 importcmd/xxx—— 这会立刻触发循环引用编译错误 - 配置结构体(如
type Config struct { DB *DBConfig })建议放在internal/config,由cmd层解析后传入各组件 - 如果用了 Wire 生成依赖注入代码,
wire.go必须和main.go在同一目录,且//go:build wireinject注释不可少
最易被忽略的是测试时的包可见性:写集成测试想 mock infra/mysql,结果发现它被 internal/service 直接 new 出来 —— 这说明初始化逻辑没收口到 cmd 层,重构成本会陡增。










