beego 不原生支持 ddd,因其 mvc 默认结构导致包耦合、依赖倒置失效及领域逻辑污染;需放弃 models/ 目录,采用 internal/presentation/application/domain/infrastructure 分层,并严守依赖方向与事务边界。

Beego 框架本身不原生支持 DDD 分层,强行套用会导致包耦合混乱、依赖倒置失效、领域逻辑被 controller 或 orm 污染——这不是 Beego 的问题,而是没切断 MVC 与 DDD 的语义冲突。
为什么 Beego 的默认结构天然抵触 DDD
Beego 的 controllers 和 models 目录是强绑定的:一个 controller 方法通常直接 new 一个 model、调用 Insert/Update、再 return JSON。这等于把领域行为(如“用户充值需校验余额上限”)塞进数据访问层,也把事务边界交给了 HTTP 请求生命周期。
常见错误现象包括:
- domain 层代码里出现
beego.Context或orm.Ormer - application 层函数返回
*models.User而非 domain 对象(如*user.User) - infrastructure 层的 repository 实现里直接写 SQL 或调用
orm.QueryTable,却没封装成接口
Beego 项目中真正可行的 DDD 分包方式
必须放弃 Beego 默认的 models/ 目录,也不要把 domain 对象放在 controllers/ 同级。推荐采用以下物理结构(与 Beego 路由注册解耦):
myapp/ ├── main.go ├── routers/ │ └── router.go // 仅注册路由,不写业务逻辑 ├── internal/ │ ├── presentation/ // 替代原 controllers/,只做参数解析 + DTO 转换 │ │ └── user/ │ │ └── handler.go // 接收 beego.Context,调用 application.UserService │ ├── application/ // 编排协调,不碰数据库、不碰 HTTP 上下文 │ │ └── user/ │ │ └── service.go // 定义 UseCase 接口,如 CreateUser、Deposit │ ├── domain/ // 纯业务逻辑,无框架依赖 │ │ └── user/ │ │ ├── entity.go // User 结构体 + 领域方法(如 u.Deposit(amount) error) │ │ └── event.go // DomainEvent 类型,如 UserDeposited │ └── infrastructure/ // 具体实现,可替换(ORM、HTTP client、缓存) │ ├── repository/ // interface 定义在 domain 或 application,实现在这里 │ │ └── user.go // UserRepository 接口实现,用 beego orm 封装 │ └── bus/ // 内存总线或消息队列适配器,发布 domain event └── api/ // OpenAPI 定义、DTO 结构体(非 domain entity)
关键点:
-
presentation层必须通过 interface 依赖application层,不能 import 其具体实现 -
domain层禁止 import 任何外部库(beego、orm、log都不行) -
infrastructure/repository/user.go可以 importgithub.com/beego/beego/v2/client/orm,但它的接口定义(如UserRepository)应放在application或domain中
重构时最容易踩的三个坑
从现有 Beego 项目迁移到 DDD 分层,不是改目录名就能完事。真实卡点往往藏在细节里:
-
事务控制丢失:Beego 默认每个请求一个 DB 连接,但 DDD 要求在 application 层统一开启/提交事务。必须把
orm.RunTransaction提到application.UserService.Create开头,并将 repository 方法签名改为接收orm.Ormer参数,而非内部自己orm.NewOrm() -
错误类型混用:Beego 常用
errors.New("xxx")或fmt.Errorf,但 domain 层应定义明确的领域错误,如user.ErrInsufficientBalance,且不能被 infrastructure 层的数据库错误(如orm.ErrNoRows)覆盖 -
DTO 与 Entity 泄露:别让
api.UserCreateReq直接嵌套domain.User字段;二者字段名、校验规则、序列化行为都不同。必须显式转换,哪怕只是简单赋值——这是隔离变化的最小成本
最常被忽略的其实是领域事件的传播时机:Beego 的 FinishRouter 钩子不适合发 domain event,因为此时 transaction 可能已提交或回滚。真正的发布点必须在 application 层业务逻辑结束、事务 commit 成功之后,靠内存总线或消息队列确保最终一致性。











