从 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
Go 项目结构演进:MVC → 分层 → DDD 限界上下文 阶段一:MVC 三层 Controller(控制器) Model(模型) View(视图) 阶段二:分层架构 Handler(表示层) Service(应用层) Repository(基础设施) 单向依赖,职责分离 阶段三:DDD 限界上下文 User 上下文 Order 上下文 Payment 上下文 每个上下文独立分层 关键演进对比 • MVC:Controller 直接操作 Model,业务逻辑与数据访问耦合 • 分层:Handler → Service → Repository,单向依赖,可测试性提升 • DDD:按业务边界划分限界上下文,每个上下文内部再分层 • 模块间通过事件(Event)或接口(Interface)通信,避免直接依赖 • 共享内核(Shared Kernel)存放跨上下文共用的值对象和基础类型 • 每个限界上下文可独立演进、独立部署(微服务拆分的基础)

DDD 分层架构的核心原则

  1. 依赖倒置(Dependency Inversion):领域层定义仓储接口,基础设施层实现该接口。领域层不依赖任何外部框架或数据库驱动。
  2. 领域层纯净:domain/ 包中只包含实体、值对象、领域服务和仓储接口,不引入任何 import 外部包(如 gorm、redis)。
  3. 应用层编排:application/ 包负责用例的编排,如开启事务、调用领域服务、发布事件,但不包含业务规则。
  4. 基础设施可替换: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 项目中,建议从分层架构起步,当业务模块间的耦合开始影响开发效率时,再引入限界上下文进行模块拆分。