真正的解耦靠包结构、接口定义和依赖流向共同约束;domain包承载全部业务规则,application层只调用领域方法,跨模块调用必须通过interface,路由按模块封装,包名按业务能力组织且domain不依赖外部库。

业务逻辑塞进 handler 或 service 包里,就不是高内聚低耦合——它只是“能跑”,不是“好改、好测、好换”。真正的解耦,靠的是包结构、接口定义和依赖流向三者共同约束出来的习惯。
domain 包必须承载全部业务规则
订单能不能创建、库存够不够、状态是否允许取消——这些判断不能出现在 controller 里,也不能藏在 service 的 if-else 中。它们属于领域语义,该由 domain/order 自己决定。
-
domain/order/Order.go定义Order结构体,所有校验逻辑写在Place()、Cancel()方法内部 -
application层只调用order.Place(),不传一堆参数去算“能不能下单” - 如果
Place()返回 error,说明业务规则没过;返回 nil,说明通过——上层不关心具体哪条规则失败 - 测试时直接 new 一个
Order,调Place()就行,不用 mock 数据库或 HTTP client
跨模块调用必须走 interface,禁止 import 具体实现包
订单完成要通知支付,但订单代码绝不能 import payment/internal。否则换微信支付为支付宝时,订单模块就得动,编译都过不了。
- 在
payment/api定义Notifier接口:NotifyOrderPaid(orderID string) error - 订单的
Complete()方法接收payment.Notifier作为参数,只调接口方法 - 启动时(比如
main.go或wire.go)注入具体实现:wechatNotifier{}或alipayNotifier{} - 编译期就能验证依赖是否断裂;删掉整个
payment模块,订单代码照样能编译
Gin 路由注册必须按模块封装,别堆在 main.go
把所有 r.POST("/order/create", ...) 写在 main.go 里,等于把路由和业务逻辑焊死在一起。Gin 不支持 Django 的 include(),但可以用 RouterGroup + 函数封装模拟。
- 每个业务模块(如
order)提供自己的注册函数:func RegisterOrderRoutes(rg *gin.RouterGroup) -
router/router.go统一调用:RegisterOrderRoutes(r.Group("/v1"))、RegisterUserRoutes(r.Group("/v1")) - 路由分组路径(如
/v1)、中间件(如 auth)由注册方外部传入,不硬编码在模块内 - 新增模块只需加一行注册调用,不碰已有路由逻辑,也不改
main.go
包名即契约,别用 controller/service/dao 这类技术分层命名
Go 没有命名空间,import 路径就是模块边界。如果所有订单相关代码散落在 order/controller、user/service、inventory/repository,你就永远不知道“下单”这件事到底牵扯哪些包。
- 按业务能力组织包:
internal/domain/order、internal/application/order、internal/infrastructure/persistence/mysql - 用
internal隔离实现,对外只暴露api或domain下的接口 -
model和dto不是通用层——domain/order里的Order是领域模型,application/order/dto是应用层数据传输对象,二者职责不同,不能混用 - 包名越具体越好:
domain/payment比domain/service更清晰;infrastructure/cache/redis比infrastructure/cache更明确
最难的不是写 interface,而是坚持让 domain 包不依赖任何外部东西——它不能 import gorm、不能 import http、甚至不能 import time(用 time.Time 可以,但别调 time.Now())。一旦破戒,高内聚就塌了一半。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











