从 MVC 到 DDD 分层架构的演变过程
Go 项目的结构设计并非一蹴而就,而是随着业务复杂度的提升不断演进。理解从经典 MVC 到 DDD 分层架构的演变路径,能帮助你根据项目阶段选择最合适的组织方式,避免过度设计或架构腐化。
第一阶段:经典 MVC 三层架构
在项目初期或业务逻辑简单的场景下,MVC(Model-View-Controller)是最常见的选择。Go 标准库 net/http 配合模板引擎即可快速搭建。
典型目录结构:
myapp/
├── main.go
├── controllers/
│ └── user_controller.go
├── models/
│ └── user.go
├── views/
│ └── user/
│ ├── list.html
│ └── detail.html
├── routes/
│ └── router.go
└── config/
└── config.go
MVC 的核心问题:
- 模型(Model)职责过重:数据库操作、业务规则、验证逻辑全部堆积在
models/包中,导致“胖模型”难以维护。 - 控制器(Controller)逻辑膨胀:随着业务增长,控制器中开始混入事务管理、权限校验、数据组装等非展示逻辑。
- 缺乏业务边界:所有业务逻辑平铺在全局包中,模块间依赖关系混乱,修改一处可能引发连锁故障。
适用场景:CRUD 密集型应用、原型验证、团队规模小于 3 人的早期项目。
第二阶段:分层架构的引入
为了解决 MVC 的职责混乱问题,团队开始引入经典的四层架构:表示层 → 应用层 → 领域层 → 基础设施层。Go 社区通常将其简化为 handler → service → repository 模式。
演进后的目录结构:
myapp/
├── cmd/
│ └── server/
│ └── main.go
├── internal/
│ ├── handler/ # 表示层:HTTP 处理、参数校验、响应组装
│ │ └── user_handler.go
│ ├── service/ # 应用层:用例编排、事务管理、事件发布
│ │ └── user_service.go
│ ├── repository/ # 基础设施层:数据库访问、外部 API 调用
│ │ └── user_repository.go
│ └── model/ # 领域层:实体定义、值对象、业务规则
│ └── user.go
├── pkg/ # 共享工具包
│ └── errors/
└── go.mod
分层架构带来的改进:
| 维度 | MVC 三层 | 分层架构 |
|---|---|---|
| 职责分离 | Model 承担所有数据与业务逻辑 | 领域层专注业务规则,Repository 专注持久化 |
| 依赖方向 | Controller → Model(双向依赖常见) | Handler → Service → Repository(单向依赖) |
| 测试难度 | 需 mock 数据库和 HTTP 上下文 | Service 层可独立单元测试,Repository 可替换为 mock |
| 业务表达 | 业务规则散落在 Controller 和 Model 中 | 领域层集中表达业务概念与不变量 |
第三阶段:DDD 限界上下文与模块划分
当单体应用膨胀到多个业务模块(如用户、订单、支付、库存)时,单一的分层架构开始暴露出新的问题:所有业务逻辑仍然在同一个代码仓库中互相引用,模块间的耦合度并未真正降低。
DDD 引入限界上下文(Bounded Context)作为最高级别的模块划分单元。每个限界上下文拥有独立的领域模型、数据库 schema 和部署单元(可选)。
DDD 模块划分后的目录结构:
myapp/
├── cmd/
│ └── server/
│ └── main.go
├── internal/
│ ├── user/ # 用户限界上下文
│ │ ├── domain/ # 领域层
│ │ │ ├── user.go # 实体
│ │ │ ├── user_repository.go# 仓储接口
│ │ │ └── user_service.go # 领域服务
│ │ ├── application/ # 应用层
│ │ │ └── register_user.go # 用例
│ │ ├── infrastructure/ # 基础设施层
│ │ │ ├── mysql_repository.go
│ │ │ └── event_publisher.go
│ │ └── interfaces/ # 表示层
│ │ └── http_handler.go
│ ├── order/ # 订单限界上下文
│ │ ├── domain/
│ │ ├── application/
│ │ ├── infrastructure/
│ │ └── interfaces/
│ └── payment/ # 支付限界上下文
│ ├── domain/
│ ├── application/
│ ├── infrastructure/
│ └── interfaces/
├── pkg/
│ └── shared/ # 共享内核(值对象、基础类型)
└── go.mod
DDD 分层架构的核心原则
- 依赖倒置(Dependency Inversion):领域层定义仓储接口,基础设施层实现该接口。领域层不依赖任何外部框架或数据库驱动。
- 领域层纯净:
domain/包中只包含实体、值对象、领域服务和仓储接口,不引入任何import外部包(如gorm、redis)。 - 应用层编排:
application/包负责用例的编排,如开启事务、调用领域服务、发布事件,但不包含业务规则。 - 基础设施可替换:
infrastructure/包实现仓储接口,可随时切换 MySQL、PostgreSQL 或 MongoDB,不影响领域层。
常见陷阱:不要将 DDD 的限界上下文直接等同于微服务。限界上下文是逻辑边界,微服务是部署边界。在单体应用中,多个限界上下文可以共存于同一个进程内,通过 Go 的接口进行隔离。
演进路径总结
| 架构阶段 | 模块划分依据 | 依赖管理 | 业务复杂度上限 | 推荐 Go 项目规模 |
|---|---|---|---|---|
| MVC 三层 | 技术角色(controller/model/view) | 无强制约束 | 低(< 5 个业务模块) | 小型工具、原型 |
| 分层架构 | 技术层次(handler/service/repo) | 单向依赖 | 中(5-15 个业务模块) | 中型 Web 服务 |
| DDD 限界上下文 | 业务领域(user/order/payment) | 依赖倒置 + 事件驱动 | 高(15+ 个业务模块) | 大型企业应用 |
从 MVC 到 DDD 的演进不是简单的“替换”,而是职责的逐步细化和边界的清晰化。在 Go 项目中,建议从分层架构起步,当业务模块间的耦合开始影响开发效率时,再引入限界上下文进行模块拆分。