领域驱动设计实战:从战略建模到战术落地的完整指南

系统讲解领域驱动设计(DDD)的核心概念与实践方法,涵盖限界上下文划分、聚合根设计、领域事件、CQRS模式等关键技术,提供Go语言实战代码与微服务拆分案例。

引言

领域驱动设计(Domain-Driven Design,DDD)是应对复杂业务系统的一套方法论。它强调以业务领域为核心,通过统一语言和明确边界来组织代码结构。

本文将从战略设计到战术实现,系统介绍DDD的核心概念,并提供Go语言的实战代码。

战略设计:划分限界上下文

限界上下文(Bounded Context)

限界上下文是业务能力的边界,不同的上下文可以使用不同的模型和术语。

电商平台限界上下文划分:

┌─────────────────┐  ┌─────────────────┐  ┌─────────────────┐
│   用户上下文     │  │   商品上下文     │  │   订单上下文     │
│                 │  │                 │  │                 │
│ - 用户注册      │  │ - 商品发布      │  │ - 下单          │
│ - 身份认证      │  │ - 库存管理      │  │ - 支付          │
│ - 权限管理      │  │ - 分类管理      │  │ - 物流跟踪      │
│                 │  │                 │  │                 │
│ 术语:用户、    │  │ 术语:商品、    │  │ 术语:订单、    │
│      账户       │  │      SKU        │  │      支付单     │
└─────────────────┘  └─────────────────┘  └─────────────────┘

上下文映射(Context Mapping)

定义不同限界上下文之间的交互模式。

// 上下文映射关系类型
type ContextMappingType string

const (
    Partnership      ContextMappingType = "Partnership"      // 合作关系
    SharedKernel     ContextMappingType = "SharedKernel"     // 共享内核
    CustomerSupplier ContextMappingType = "CustomerSupplier" // 客户-供应商
    Conformist       ContextMappingType = "Conformist"       // 遵奉者
    AntiCorruptionLayer ContextMappingType = "AntiCorruptionLayer" // 防腐层
    PublishedLanguage   ContextMappingType = "PublishedLanguage"   // 发布语言
)

// 订单上下文与用户上下文的映射
var orderToUserMapping = ContextMapping{
    From: "OrderContext",
    To:   "UserContext",
    Type: CustomerSupplier, // 订单上下文是客户,用户上下文是供应商
    Description: "订单上下文需要获取用户信息,通过用户上下文提供的API获取",
}

防腐层(Anti-Corruption Layer)

在边界处转换外部模型,防止外部概念污染内部领域模型。

// 防腐层:将外部用户服务的模型转换为内部模型
type UserACL struct {
    userServiceClient *UserServiceClient
}

// 外部模型
type ExternalUser struct {
    ID        string
    Username  string
    EmailAddr string
    UserLevel int
}

// 内部领域模型
type User struct {
    ID       UserID
    Username string
    Email    string
    Level    UserLevel
}

func (acl *UserACL) GetUser(ctx context.Context, userID UserID) (*User, error) {
    // 调用外部服务
    externalUser, err := acl.userServiceClient.GetUser(ctx, string(userID))
    if err != nil {
        return nil, err
    }
    
    // 转换为内部模型
    return &User{
        ID:       UserID(externalUser.ID),
        Username: externalUser.Username,
        Email:    externalUser.EmailAddr,
        Level:    acl.convertUserLevel(externalUser.UserLevel),
    }, nil
}

func (acl *UserACL) convertUserLevel(externalLevel int) UserLevel {
    switch externalLevel {
    case 1:
        return UserLevelNormal
    case 2:
        return UserLevelVIP
    case 3:
        return UserLevelSVIP
    default:
        return UserLevelNormal
    }
}

战术设计:核心模式

实体(Entity)

具有唯一标识,通过标识区分,而非属性值。

// 订单实体
type Order struct {
    ID         OrderID       // 唯一标识
    UserID     UserID
    Items      []OrderItem
    Status     OrderStatus
    CreatedAt  time.Time
    UpdatedAt  time.Time
    
    version    int // 乐观锁版本号
}

// 订单ID值对象
type OrderID string

func NewOrderID() OrderID {
    return OrderID(uuid.New().String())
}

// 实体相等性判断基于ID
func (o *Order) Equals(other *Order) bool {
    return o.ID == other.ID
}

值对象(Value Object)

没有唯一标识,通过属性值相等判断,不可变。

// 金额值对象(不可变)
type Money struct {
    Amount   int    // 分为单位,避免浮点精度问题
    Currency string
}

func NewMoney(amount int, currency string) Money {
    if amount < 0 {
        panic("金额不能为负数")
    }
    return Money{Amount: amount, Currency: currency}
}

// 值对象相等性判断基于属性
func (m Money) Equals(other Money) bool {
    return m.Amount == other.Amount && m.Currency == other.Currency
}

// 值对象操作返回新实例
func (m Money) Add(other Money) Money {
    if m.Currency != other.Currency {
        panic("币种不一致")
    }
    return NewMoney(m.Amount+other.Amount, m.Currency)
}

// 地址值对象
type Address struct {
    Province string
    City     string
    District string
    Detail   string
}

func (a Address) Equals(other Address) bool {
    return a.Province == other.Province &&
        a.City == other.City &&
        a.District == other.District &&
        a.Detail == other.Detail
}

聚合根(Aggregate Root)

聚合是一组相关对象的集合,聚合根是外部访问聚合的唯一入口。

// 订单聚合根
type Order struct {
    ID         OrderID
    UserID     UserID
    Items      []OrderItem
    Status     OrderStatus
    TotalAmount Money
    Address    Address
    
    version    int
    domainEvents []DomainEvent // 领域事件集合
}

// 订单项(聚合内部实体)
type OrderItem struct {
    ProductID   ProductID
    ProductName string
    Quantity    int
    UnitPrice   Money
}

// 聚合根方法:添加订单项
func (o *Order) AddItem(product Product, quantity int) error {
    if o.Status != OrderStatusPending {
        return errors.New("只能向待支付订单添加商品")
    }
    
    if quantity <= 0 {
        return errors.New("商品数量必须大于0")
    }
    
    item := OrderItem{
        ProductID:   product.ID,
        ProductName: product.Name,
        Quantity:    quantity,
        UnitPrice:   product.Price,
    }
    
    o.Items = append(o.Items, item)
    o.recalculateTotal()
    
    // 记录领域事件
    o.AddDomainEvent(OrderItemAddedEvent{
        OrderID:   o.ID,
        ProductID: product.ID,
        Quantity:  quantity,
    })
    
    return nil
}

// 聚合根方法:确认订单
func (o *Order) Confirm() error {
    if o.Status != OrderStatusPending {
        return errors.New("只能确认待支付订单")
    }
    
    if len(o.Items) == 0 {
        return errors.New("订单至少包含一个商品")
    }
    
    o.Status = OrderStatusConfirmed
    o.AddDomainEvent(OrderConfirmedEvent{
        OrderID:     o.ID,
        UserID:      o.UserID,
        TotalAmount: o.TotalAmount,
    })
    
    return nil
}

// 聚合根负责维护内部一致性
func (o *Order) recalculateTotal() {
    total := NewMoney(0, "CNY")
    for _, item := range o.Items {
        itemTotal := NewMoney(item.UnitPrice.Amount*item.Quantity, item.UnitPrice.Currency)
        total = total.Add(itemTotal)
    }
    o.TotalAmount = total
}

// 聚合根管理领域事件
func (o *Order) AddDomainEvent(event DomainEvent) {
    o.domainEvents = append(o.domainEvents, event)
}

func (o *Order) ClearDomainEvents() {
    o.domainEvents = nil
}

仓储模式(Repository)

提供聚合的持久化接口,隐藏底层存储细节。

// 订单仓储接口(领域层定义)
type OrderRepository interface {
    GetByID(ctx context.Context, id OrderID) (*Order, error)
    Save(ctx context.Context, order *Order) error
    Update(ctx context.Context, order *Order) error
}

// 仓储实现(基础设施层)
type orderRepositoryImpl struct {
    db *gorm.DB
}

func (r *orderRepositoryImpl) GetByID(ctx context.Context, id OrderID) (*Order, error) {
    var orderPO OrderPO
    if err := r.db.WithContext(ctx).Where("id = ?", id).First(&orderPO).Error; err != nil {
        return nil, err
    }
    
    // PO转换为领域对象
    return orderPO.toDomain(), nil
}

func (r *orderRepositoryImpl) Save(ctx context.Context, order *Order) error {
    // 领域对象转换为PO
    orderPO := fromDomain(order)
    
    return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
        if err := tx.Create(orderPO).Error; err != nil {
            return err
        }
        
        // 发布领域事件
        for _, event := range order.domainEvents {
            if err := r.publishEvent(ctx, tx, event); err != nil {
                return err
            }
        }
        
        order.ClearDomainEvents()
        return nil
    })
}

领域服务(Domain Service)

不属于任何实体的业务操作。

// 订单服务(领域服务)
type OrderService struct {
    orderRepo     OrderRepository
    inventorySvc  InventoryService
    paymentSvc    PaymentService
}

// 创建订单(涉及多个聚合的操作)
func (s *OrderService) CreateOrder(ctx context.Context, cmd CreateOrderCommand) (*Order, error) {
    // 1. 检查库存
    for _, item := range cmd.Items {
        available, err := s.inventorySvc.CheckStock(ctx, item.ProductID, item.Quantity)
        if err != nil {
            return nil, err
        }
        if !available {
            return nil, errors.New("库存不足")
        }
    }
    
    // 2. 创建订单聚合
    order := &Order{
        ID:     NewOrderID(),
        UserID: cmd.UserID,
        Status: OrderStatusPending,
    }
    
    // 3. 添加订单项
    for _, item := range cmd.Items {
        product, err := s.getProduct(ctx, item.ProductID)
        if err != nil {
            return nil, err
        }
        if err := order.AddItem(product, item.Quantity); err != nil {
            return nil, err
        }
    }
    
    // 4. 保存订单
    if err := s.orderRepo.Save(ctx, order); err != nil {
        return nil, err
    }
    
    return order, nil
}

领域事件(Domain Event)

表示领域中发生的重要事件。

// 领域事件基类
type DomainEvent interface {
    EventName() string
    OccurredAt() time.Time
}

// 订单确认事件
type OrderConfirmedEvent struct {
    OrderID     OrderID
    UserID      UserID
    TotalAmount Money
    occurredAt  time.Time
}

func (e OrderConfirmedEvent) EventName() string {
    return "order.confirmed"
}

func (e OrderConfirmedEvent) OccurredAt() time.Time {
    return e.occurredAt
}

// 事件处理器
type OrderEventHandler struct {
    inventorySvc InventoryService
    notifySvc    NotificationService
}

func (h *OrderEventHandler) HandleOrderConfirmed(ctx context.Context, event OrderConfirmedEvent) error {
    // 1. 扣减库存
    if err := h.inventorySvc.DeductStock(ctx, event.OrderID); err != nil {
        return err
    }
    
    // 2. 发送通知
    if err := h.notifySvc.SendOrderConfirmation(ctx, event.UserID, event.OrderID); err != nil {
        return err
    }
    
    return nil
}

CQRS模式

命令与查询分离

将系统的读操作和写操作分离,分别优化。

// 命令端(写操作)
type OrderCommandService struct {
    orderRepo OrderRepository
    eventBus  EventBus
}

func (s *OrderCommandService) CreateOrder(ctx context.Context, cmd CreateOrderCommand) error {
    order, err := s.createOrderAggregate(ctx, cmd)
    if err != nil {
        return err
    }
    
    if err := s.orderRepo.Save(ctx, order); err != nil {
        return err
    }
    
    // 发布领域事件
    for _, event := range order.domainEvents {
        s.eventBus.Publish(ctx, event)
    }
    
    return nil
}

// 查询端(读操作)
type OrderQueryService struct {
    db *gorm.DB // 直接查询读数据库
}

type OrderDTO struct {
    ID          string
    UserID      string
    Status      string
    TotalAmount int
    Items       []OrderItemDTO
    CreatedAt   time.Time
}

func (s *OrderQueryService) GetOrder(ctx context.Context, orderID string) (*OrderDTO, error) {
    var order OrderDTO
    
    err := s.db.WithContext(ctx).
        Table("orders").
        Select("id, user_id, status, total_amount, created_at").
        Where("id = ?", orderID).
        Scan(&order).Error
    
    if err != nil {
        return nil, err
    }
    
    // 查询订单项
    var items []OrderItemDTO
    err = s.db.WithContext(ctx).
        Table("order_items").
        Where("order_id = ?", orderID).
        Scan(&items).Error
    
    order.Items = items
    return &order, nil
}

func (s *OrderQueryService) ListOrders(ctx context.Context, userID string, page, pageSize int) ([]OrderDTO, error) {
    var orders []OrderDTO
    
    err := s.db.WithContext(ctx).
        Table("orders").
        Where("user_id = ?", userID).
        Order("created_at DESC").
        Offset((page - 1) * pageSize).
        Limit(pageSize).
        Scan(&orders).Error
    
    return orders, err
}

总结

领域驱动设计提供了一套应对复杂业务系统的方法论:

  1. 战略设计:通过限界上下文划分业务边界,通过上下文映射定义交互模式
  2. 战术设计:使用实体、值对象、聚合根、领域服务等模式构建领域模型
  3. CQRS:分离读写操作,分别优化性能

DDD的核心价值在于:让代码结构反映业务结构,降低沟通成本,提升系统可维护性。

延伸阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页