AI DDD重构实践

电商系统 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 重构方法论:小步前进

我们采用 **"小步重构"** 策略,每次改动最小化,可独立提交:

```

  1. 代码改动(聚焦一个具体点)

  2. 运行测试(72 个测试用例)

  3. 如果通过 → git commit

  4. 如果失败 → 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 小步重构策略

每个重构步骤遵循:

  1. **最小改动** - 每次改动最小化,可独立提交

  2. **逻辑不变** - 重构不改变业务行为

  3. **测试验证** - 每步完成后执行 72 个测试用例

  4. **可回滚** - 每步都是独立提交,出问题可快速回退

```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 重构:

  1. **先理解业务** - DDD 的核心是领域模型,不是技术

  2. **从小步开始** - 不要试图一次性重构整个项目

  3. **保持测试通过** - 测试是重构的安全网

  4. **团队共识** - DDD 需要团队共同理解和维护


十、参考资源

  • **项目源码**: 私有仓库

  • **DDD 参考**: 《领域驱动设计》Eric Evans

  • **Go DDD 实践**: 《Go 语言高级编程》

  • **架构模式**: 《企业应用架构模式》Martin Fowler


**感谢阅读!** 如果你对 DDD 重构有任何问题,欢迎在评论区留言讨论。


附录:重构统计

```

重构周期: 2025年 - 2026年

重构步骤: 70+ 个小步重构

测试用例: 72 个(全部通过)

代码提交: 70+ 次独立 commit

DDD 成熟度: 98-99%

```


*本文作者:后端架构团队*

*发布日期:2026年2月*

*版权声明:欢迎转载,请注明出处*

相关推荐
Grassto2 天前
12 go.sum 是如何保证依赖安全的?校验机制源码解析
安全·golang·go·哈希算法·go module
Grassto3 天前
11 Go Module 缓存机制详解
开发语言·缓存·golang·go·go module
程序设计实验室4 天前
2025年的最后一天,分享我使用go语言开发的电子书转换工具网站
go
我的golang之路果然有问题4 天前
使用 Hugo + GitHub Pages + PaperMod 主题 + Obsidian 搭建开发博客
golang·go·github·博客·个人开发·个人博客·hugo
啊汉6 天前
古文观芷App搜索方案深度解析:打造极致性能的古文搜索引擎
go·软件随想
asaotomo7 天前
一款 AI 驱动的新一代安全运维代理 —— DeepSentry(深哨)
运维·人工智能·安全·ai·go
码界奇点7 天前
基于Gin与GORM的若依后台管理系统设计与实现
论文阅读·go·毕业设计·gin·源代码管理
迷迭香与樱花7 天前
Gin 框架
go·gin
只是懒得想了8 天前
用Go通道实现并发安全队列:从基础到最佳实践
开发语言·数据库·golang·go·并发安全