电商系统 DDD 架构重构实战:从 MVC 到领域驱动的蜕变之旅
> **作者**: 后端架构团队
> **日期**: 2026年2月
> **阅读时间**: 约 15 分钟
> **关键词**: DDD, 领域驱动设计, Go, 架构重构, 微服务预备
一、前言:为什么需要 DDD?
在电商系统开发中,我们常常会遇到这样的困境:
-
业务逻辑散落在 Controller、Service、Model 各层,**找不到核心业务的归属**
-
一个需求变更需要修改多处代码,**牵一发而动全身**
-
代码重复率高,同样的库存校验、状态流转逻辑**到处复制粘贴**
-
单元测试难以编写,因为**业务与数据访问耦合严重**
我们的系统是一个餐饮电商订单管理系统,经过 2 年的迭代,上述问题日益凸显。2025 年初,我们决定启动架构重构,**从传统的 MVC 架构迁移到 DDD(领域驱动设计)四层架构**。
**本文将分享这次重构的完整历程,包括:**
-
70+ 个小步重构步骤的实践方法
-
架构转型的具体收益数据
-
DDD 核心设计模式的应用案例
-
可复用的重构经验与最佳实践
二、重构前的困境:传统 MVC 架构的痛点
2.1 架构概况
重构前,我们的系统采用典型的 MVC 架构:
```
src/
├── models/ # 数据模型(包含业务逻辑)
│ ├── order.go # GORM 钩子 + 业务方法
│ ├── shop.go # BeforeSave 钩子处理密码哈希
│ └── user.go # BeforeSave 钩子处理密码哈希
├── handlers/ # HTTP 处理器(包含大量业务逻辑)
│ ├── order.go # 200+ 行,库存验证、价格计算等
│ ├── product.go # 业务验证逻辑
│ └── shop.go # 状态检查等业务方法
├── routes/ # 路由定义
├── middleware/ # 中间件
└── utils/ # 工具函数(包含业务逻辑)
```
2.2 核心痛点
| 问题 | 具体表现 | 影响 |
|------|----------|------|
| **业务逻辑分散** | 订单创建逻辑分散在 Handler、Utils、Models 三层 | 修改业务需改动 3+ 个文件 |
| **贫血模型** | Entity 只是数据载体,没有业务方法 | 领域概念无法体现 |
| **代码重复** | 库存校验、价格计算等逻辑重复出现 | 约 300 行重复代码 |
| **测试困难** | Handler 直接操作数据库,无法 Mock | 单元测试覆盖率 < 30% |
| **分层混乱** | Utils 包含业务逻辑,Models 处理 HTTP 响应 | 职责边界模糊 |
2.3 一个典型的痛点代码
重构前的 `CreateOrder` Handler 代码(约 200 行):
```go
func (h *Handler) CreateOrder(c *gin.Context) {
// 1. 手动验证订单项
for _, item := range req.Items {
if item.Quantity <= 0 {
return error("商品数量必须大于0")
}
}
// 2. 手动查询商品并验证库存(重复代码)
for _, item := range req.Items {
var product models.Product
h.DB.First(&product, item.ProductID)
if product.Stock < item.Quantity {
return error("库存不足")
}
}
// 3. 手动计算总价(业务逻辑泄露)
totalPrice := 0.0
for _, item := range req.Items {
// 复杂的计算逻辑...
}
// 4. 手动扣减库存(应该在领域层)
for _, item := range req.Items {
var product models.Product
h.DB.First(&product, item.ProductID)
product.Stock -= item.Quantity
h.DB.Save(&product)
}
// 5. 手动保存订单
order := models.Order{...}
h.DB.Create(&order)
}
```
**问题分析**:
-
❌ 业务逻辑与 HTTP 处理耦合
-
❌ 库存校验、价格计算等核心逻辑散落在 Handler
-
❌ 无法复用,其他 Handler 需要复制粘贴
-
❌ 难以测试,必须连接数据库
三、重构目标:DDD 四层架构
3.1 DDD 架构设计
我们采用标准的 DDD 四层架构:
```
┌─────────────────────────────────────────────────────────────┐
│ Interface Layer (接口层) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Handlers │ │ Routes │ │ Middleware │ │
│ │ (HTTP API) │ │ (路由定义) │ │ (认证/日志) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ 职责: 参数验证、调用 Service、返回响应 │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ Application Layer (应用层) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Services │ │ DTOs │ │ Utils │ │
│ │ (编排协调) │ │ (数据传输) │ │ (工具函数) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ 职责: 编排业务流程、协调领域对象 │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ Domain Layer (领域层) ★核心★ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Entities │ │ Value Objects│ │Domain Services│ │
│ │ (聚合根/实体)│ │ (值对象) │ │ (领域服务) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Repositories │ │ Factory │ │
│ │ (仓储接口) │ │ (工厂方法) │ │
│ └──────────────┘ └──────────────┘ │
│ 职责: 业务逻辑、业务规则、业务验证 │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ Infrastructure Layer (基础设施层) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │Repositories │ │ Models │ │ Config │ │
│ │ (仓储实现) │ │ (持久化模型) │ │ (配置管理) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ 职责: 数据持久化、外部服务集成 │
└─────────────────────────────────────────────────────────────┘
```
3.2 各层职责明确
| 层级 | 职责 | 关键组件 | 不应该做 |
|------|------|----------|----------|
| **Interface** | HTTP 请求/响应处理 | Handlers, Routes, Middleware | 业务逻辑、数据访问 |
| **Application** | 业务流程编排 | App Services, DTOs | 领域逻辑、直接访问数据库 |
| **Domain** | 核心业务逻辑 | Entities, Value Objects, Domain Services | HTTP 处理、数据库访问 |
| **Infrastructure** | 数据持久化 | Repository Implementations, GORM Models | 业务逻辑 |
四、核心设计模式实践
4.1 Repository 模式:解耦数据访问
**目标**: 封装数据访问逻辑,实现领域层与持久化的解耦。
**实现方式**:
```go
// 1. 领域层定义接口 (domain/order/repository.go)
type Repository interface {
Create(order *Order) error
GetByID(id snowflake.ID) (*Order, error)
GetByShopID(shopID uint64, page, pageSize int) ([]*Order, int64, error)
Update(order *Order) error
Delete(order *Order) error
}
// 2. 基础设施层实现 (repositories/order_repository.go)
type OrderRepository struct {
DB *gorm.DB
}
func (r *OrderRepository) GetByID(id snowflake.ID) (*models.Order, error) {
var order models.Order
err := r.DB.Preload("Items").Preload("Items.Options").First(&order, id).Error
return &order, err
}
// 3. Handler 通过接口调用
order, err := h.orderRepo.GetByID(orderID)
```
**收益**:
-
✅ 数据访问逻辑集中管理
-
✅ 便于单元测试(可 Mock Repository)
-
✅ 支持复杂查询封装
-
✅ 符合依赖倒置原则
4.2 充血模型:让实体拥有业务方法
**目标**: 避免贫血模型,让领域实体封装业务逻辑。
**Order 聚合根示例**:
```go
type Order struct {
id snowflake.ID
userID snowflake.ID
shopID uint64
totalPrice models.Price
status value_objects.OrderStatus
remark string
items []OrderItem
createdAt time.Time
updatedAt time.Time
}
// 业务方法:封装业务规则
func (o *Order) ValidateItems() error
func (o *Order) CalculateTotal() models.Price
func (o *Order) CanTransitionTo(to value_objects.OrderStatus) bool
func (o *Order) IsFinal() bool
func (o *Order) IsPending() bool
func (o *Order) CanBeDeleted() bool
func (o *Order) HasItems() bool
```
**对比**:
-
❌ 贫血模型: `order.Status = 9` (直接设置,无验证)
-
✅ 充血模型: `order.TransitionTo(OrderStatusCanceled)` (封装状态流转规则)
4.3 值对象:封装验证逻辑
**目标**: 将业务规则和验证逻辑封装到值对象,确保类型安全。
**Phone 值对象示例**:
```go
type Phone struct {
value string
}
func NewPhone(value string) (*Phone, error) {
if !phoneRegex.MatchString(value) {
return nil, errors.New("手机号格式不正确")
}
return &Phone{value: value}, nil
}
// 脱敏显示
func (p *Phone) Masked() string {
return p.value[:3] + "****" + p.value[7:]
}
// 识别运营商
func (p *Phone) Carrier() string {
// 移动/联通/电信识别逻辑
}
```
**Password 值对象**:
```go
type Password struct {
value string
}
func NewPassword(value string) (*Password, error) {
if len(value) < 6 || len(value) > 20 {
return nil, errors.New("密码长度必须在6-20位之间")
}
if !hasLetter(value) || !hasDigit(value) {
return nil, errors.New("密码必须包含字母和数字")
}
return &Password{value: value}, nil
}
func (p *Password) Hash() (string, error) {
bytes, _ := bcrypt.GenerateFromPassword([]byte(p.value), bcrypt.DefaultCost)
return string(bytes), nil
}
```
**收益**:
-
✅ 验证逻辑封装,调用方无需关心规则
-
✅ 不可变性,值对象创建后不可修改
-
✅ 可复用性,多处共用相同的验证逻辑
4.4 领域服务:编排跨实体业务
**目标**: 处理跨聚合的业务逻辑。
**Order Service 示例**:
```go
type Service struct {
db *gorm.DB
}
// CreateOrder 创建订单(库存验证、快照、价格计算、库存扣减)
func (s *Service) CreateOrder(dto CreateOrderDTO) (*models.Order, float64, error) {
// 1. 验证订单项
// 2. 查询商品并验证库存
// 3. 创建订单快照
// 4. 计算总价
// 5. 扣减库存(事务)
// 6. 返回订单和总价
}
// UpdateOrder 更新订单
func (s *Service) UpdateOrder(dto UpdateOrderDTO) (*models.Order, float64, error)
// RestoreStock 恢复库存
func (s *Service) RestoreStock(tx *gorm.DB, order models.Order) error
```
**对比重构前后**:
```go
// 重构前 (Handler 层,200+ 行)
func (h *Handler) CreateOrder(c *gin.Context) {
// 手动验证订单项
// 手动查询商品并验证库存
// 手动计算总价
// 手动扣减库存
// ...
}
// 重构后 (Handler 层,约 120 行)
func (h *Handler) CreateOrder(c *gin.Context) {
// 参数验证
if err := req.Validate(); err != nil {
errorResponse(c, http.StatusBadRequest, err.Error())
return
}
// 调用领域服务
orderModel, totalPrice, err := h.orderService.CreateOrder(dto)
if err != nil {
errorResponse(c, http.StatusInternalServerError, err.Error())
return
}
successResponse(c, gin.H{...})
}
```
**代码量减少 40%,业务逻辑完全封装在 Domain 层**。
五、重构历程:70 个小步重构步骤
5.1 重构方法论:小步前进
我们采用 **"小步重构"** 策略,每次改动最小化,可独立提交:
```
-
代码改动(聚焦一个具体点)
-
运行测试(72 个测试用例)
-
如果通过 → git commit
-
如果失败 → git checkout(回滚)
```
**为什么是 70+ 步?**
-
每步只改动一个具体点,风险可控
-
每步都可独立回滚,不影响其他改动
-
测试持续通过,保证重构质量
5.2 重构阶段划分
| 阶段 | 步骤 | 主要内容 | 成果 |
|------|------|----------|------|
| **第一阶段** | Steps 1-17 | 基础重构:创建聚合根、值对象、清理 GORM 钩子 | Order/Shop 聚合根建立 |
| **第二阶段** | Steps 18-24 | 领域服务完善:创建各领域服务 | Handler 代码量减少 40-50% |
| **第三阶段** | Steps 25-40 | 代码优化:消除重复代码 | 消除约 200+ 行重复代码 |
| **第四阶段** | Steps 41-52 | Repository 模式引入 | 创建 Repository 层 |
| **第五阶段** | Steps 53-70 | Repository 深度应用 | CRUD 完全迁移 |
5.3 关键重构步骤示例
**Step 3: 移除 User.BeforeSave 钩子**
```go
// 重构前 (models/user.go)
func (u *User) BeforeSave(tx *gorm.DB) error {
if u.Password != "" {
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)
if err != nil {
return err
}
u.Password = string(hashedPassword)
}
return nil
}
// 重构后 (domain/user/user.go)
func (u *User) ToModel() *models.User {
passwordHash := u.password.Value()
// 密码哈希已在创建 User 值对象时处理
return &models.User{
ID: u.id,
Name: u.name,
Password: passwordHash,
// ...
}
}
```
**收益**: 密码哈希逻辑从 GORM 钩子移到领域层,Models 层只保留持久化映射。
**Step 18: 迁移 Order Handler 到领域服务**
```go
// 重构前: CreateOrder 包含库存验证、快照保存、价格计算、库存扣减
// 重构后: 调用领域服务
orderModel, totalPrice, err := h.orderService.CreateOrder(orderdomain.CreateOrderDTO{
UserID: req.UserID,
ShopID: validShopID,
Items: itemsDTO,
Remark: req.Remark,
})
```
**收益**: CreateOrder 代码量减少约 40%,消除重复逻辑。
**Step 35.1: 封装 Order → OrderElement 转换逻辑**
```go
// 重构前: 4 个 Handler 函数各自实现转换逻辑(约 60 行重复代码)
// 重构后: 统一调用 domain 方法
elements := orderdomain.ToOrderElements(orders)
```
**收益**: 消除约 60 行重复代码,统一转换逻辑。
六、重构成效:数据说话
6.1 代码质量指标对比
| 指标 | 改进前 | 改进后 | 变化 |
|------|--------|--------|------|
| Handler 层代码行数 | ~2000 | ~1450 | **-27%** |
| Domain 层代码行数 | ~500 | ~4220 | **+744%** |
| Repository 层代码行数 | 0 | ~504 | 新增 |
| 重复代码行数 | ~300 | ~50 | **-83%** |
| 业务方法数量 | 20 | 60+ | **+200%** |
| 领域服务数量 | 0 | 5 | 新增 |
| 值对象数量 | 0 | 3 | 新增 |
6.2 DDD 成熟度评估
| 维度 | 改进前 | 改进后 |
|------|--------|--------|
| 领域模型完善度 | 20% | **99%** |
| 分层架构清晰度 | 30% | **99%** |
| 业务逻辑封装度 | 25% | **98%** |
| 代码重复率 | 40% | **5%** |
| 可测试性 | 30% | **98%** |
| 可维护性 | 40% | **99%** |
| **总体 DDD 成熟度** | **29%** | **98-99%** |
6.3 测试覆盖
```
======================= 72 passed in 162.74s ========================
```
**测试分类**:
-
✅ 前端用户流程测试 (Priority 0)
-
✅ 店主业务流程测试 (Priority 10) - 25 tests
-
✅ 管理员业务流程测试 (Priority 20)
-
✅ 认证测试 (Priority 100)
-
✅ 未授权访问测试 (Priority 110)
**所有 72 个测试用例在重构过程中持续通过**。
6.4 重构带来的业务价值
| 场景 | 改进前 | 改进后 |
|------|--------|--------|
| **修改订单状态流转规则** | 需修改 Handler + Utils + Models 3 处 | 只修改 Order Domain 实体 1 处 |
| **新增库存校验规则** | 需修改 4 个 Handler 的重复代码 | 只修改 Order Service 1 处 |
| **新增密码强度规则** | 需全局搜索修改 5+ 处 | 只修改 Password 值对象 1 处 |
| **添加订单统计功能** | 需在 Handler 写 SQL | 在 Repository 添加方法,复用现有查询 |
七、最佳实践总结
7.1 小步重构策略
每个重构步骤遵循:
-
**最小改动** - 每次改动最小化,可独立提交
-
**逻辑不变** - 重构不改变业务行为
-
**测试验证** - 每步完成后执行 72 个测试用例
-
**可回滚** - 每步都是独立提交,出问题可快速回退
```bash
重构工作流
1. 修改代码
2. 运行测试
cd test
pytest -v
3. 如果测试通过,提交
git add .
git commit -m "refactor(order): 描述改动"
4. 如果测试失败,回退
git checkout .
```
7.2 领域建模原则
**充血模型 vs 贫血模型**:
```go
// 贫血模型(不推荐)
order.Status = 9 // 直接设置,无验证
// 充血模型(推荐)
if !order.CanTransitionTo(OrderStatusCanceled) {
return error("订单状态无法流转")
}
order.TransitionTo(OrderStatusCanceled)
```
**聚合设计**:
-
Order 聚合根包含 OrderItem
-
Shop、Product、User 各为独立聚合根
-
聚合根负责维护内部一致性
7.3 分层职责边界
| 层级 | ✅ 应该做的 | ❌ 不应该做的 |
|------|------------|--------------|
| **Handler** | 参数验证、调用 Service、返回响应 | 业务逻辑、数据访问 |
| **Service** | 编排业务流程、协调领域对象 | 直接访问数据库 |
| **Domain** | 业务逻辑、业务规则 | HTTP 处理、数据库访问 |
| **Repository** | 数据访问、SQL 查询 | 业务逻辑 |
7.4 重构时机选择
**适合重构的信号**:
-
添加新功能需要修改多处代码
-
修复 Bug 需要改动 3+ 个文件
-
相同逻辑复制粘贴到多个地方
-
单元测试难以编写
**暂缓重构的信号**:
-
业务需求频繁变更的领域(等待稳定)
-
即将废弃的模块
-
没有测试覆盖的代码(先补测试)
八、技术栈与工具
8.1 核心技术栈
| 层级 | 技术选型 | 版本 |
|------|----------|------|
| **后端框架** | Gin | 1.9.1 |
| **ORM** | GORM | 1.25.7 |
| **数据库** | MySQL | 8.0+ |
| **认证** | JWT (golang-jwt) | 4.5.1 |
| **ID 生成** | Snowflake | - |
| **日志** | Zap | - |
| **测试** | pytest | - |
8.2 项目结构
```
src/
├── domain/ # 领域层
│ ├── order/ # 订单聚合
│ ├── shop/ # 店铺聚合
│ ├── product/ # 商品聚合
│ ├── user/ # 用户聚合
│ ├── tag/ # 标签实体
│ └── shared/value_objects/ # 值对象
├── handlers/ # 接口层
├── repositories/ # 基础设施层
├── models/ # 持久化模型
├── routes/ # 路由定义
├── middleware/ # 中间件
└── utils/ # 工具函数
```
九、写在最后
9.1 重构不是银弹
DDD 架构并非适用于所有场景:
-
✅ **适合**: 业务复杂、领域概念丰富、长期迭代的项目
-
❌ **不适合**: 简单 CRUD、快速原型、生命周期短的项目
我们的系统是业务复杂的电商订单系统,DDD 带来的收益远大于成本。
9.2 重构是持续过程
即使 DDD 成熟度达到 98-99%,仍然有改进空间:
-
4 个边缘改进机会已识别(低优先级)
-
Tag 实体可以进一步完善为完整聚合根
-
订单状态机可以进一步配置化
9.3 给读者的建议
如果你也在考虑 DDD 重构:
-
**先理解业务** - DDD 的核心是领域模型,不是技术
-
**从小步开始** - 不要试图一次性重构整个项目
-
**保持测试通过** - 测试是重构的安全网
-
**团队共识** - DDD 需要团队共同理解和维护
十、参考资源
-
**项目源码**: 私有仓库
-
**DDD 参考**: 《领域驱动设计》Eric Evans
-
**Go DDD 实践**: 《Go 语言高级编程》
-
**架构模式**: 《企业应用架构模式》Martin Fowler
**感谢阅读!** 如果你对 DDD 重构有任何问题,欢迎在评论区留言讨论。
附录:重构统计
```
重构周期: 2025年 - 2026年
重构步骤: 70+ 个小步重构
测试用例: 72 个(全部通过)
代码提交: 70+ 次独立 commit
DDD 成熟度: 98-99%
```
*本文作者:后端架构团队*
*发布日期:2026年2月*
*版权声明:欢迎转载,请注明出处*