clean architecture不适合轻量工具、cli脚本等简单场景,仅当业务含3+核心实体和5+用例、存在跨实体耦合规则(如订单校验余额/库存/优惠券)时才应启用,否则易导致分层冗余与边界黏连。
clean architecture 不适合直接套用在轻量 http 工具类、cli 小脚本、单文件原型或胶水型微服务中。 它的分层成本在业务逻辑不足 3 个核心实体 + 5 个以上用例时,会明显拖慢开发节奏,而非提升质量。
什么时候该停手:用例层未满 5 个就建完整四层
很多团队在 main.go 还没写完就急着拆出 pkg/entities、pkg/usecase、api/handlers,结果发现:CreateUser 和 GetUser 两个函数撑不起一个 UserUsecase 接口,最终所有 service.go 文件里全是直通调用,Repository 接口只有一种实现且永不替换。
- 真实信号:你写了 3 个以上
xxx_repository.go,但每个都只在mock_目录下有对应测试桩,没有第二套实现(比如内存版 vs MongoDB 版) - 真实信号:
usecase/下的结构体字段全是repo UserRepository、cache CacheClient,没有一个方法调用其他用例或编排多个实体 - 真实信号:你在写单元测试时,发现必须启动 Gin 路由才能测通一条路径——说明接口适配层和用例层边界已经黏连
什么时候必须加:领域规则开始跨实体耦合
当你的业务出现「创建订单需校验用户余额 + 库存 + 优惠券状态」这类判断,且这些校验分散在三个不同 handler 或 service 中,每次修改都要同步改三处,就到了 Clean Architecture 的发力点。
- 关键动作:把校验逻辑抽成
CanPlaceOrder函数,放在pkg/domain或pkg/entities,参数只接受user.User、product.Stock、coupon.Code等纯结构体,不带任何*sql.DB或context.Context - 关键约束:这个函数不能调用
http.Get、不能访问os.Getenv、不能 new 任何 infra 层对象——否则它就不是领域逻辑,只是又一层胶水 - 性能提醒:这种跨实体判断若高频调用,别在用例层反复查库;应在接口适配层一次查齐,再传给领域函数——Clean 架构不反对批量 IO,只反对把 IO 埋进业务规则
Go 特性带来的隐性边界:interface 不是银弹
Go 的接口是隐式实现,这会让 Clean Architecture 的依赖倒置看起来“很松”,实则容易失控。比如你在 pkg/usecase 定义了 PaymentGateway 接口,但所有实现都在 infrastructure/stripe/ 里硬编码了 stripe.Client 初始化逻辑,导致无法在测试中注入 stub。
- 典型破绽:
func NewStripePayment(pkey string) PaymentGateway—— 这个工厂函数不该出现在用例层,应上移到app/bootstrap/或main.go的依赖注入入口 - 典型破绽:
type PaymentGateway interface { Charge(ctx context.Context, amount int) error },但实际实现里Charge方法内部又调用了log.Printf或redis.Client.Set—— 这已违反单一职责,也污染了领域契约 - 可操作建议:所有 infra 层的 client 初始化、中间件注册、配置加载,必须收口到
main.go或app/下的初始化模块;用例层只接收已构造好的接口实例
最常被忽略的一点:Clean Architecture 在 Go 里真正难的不是分层,而是守住「实体不 import 任何外部包」这条线。一旦 book.go 里出现 "go.mongodb.org/mongo-driver/bson" 或 "github.com/google/uuid",整个内圈就塌了一角——后续想换数据库或序列化格式,代价远超预期。










