引言
领域驱动设计(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
}
总结
领域驱动设计提供了一套应对复杂业务系统的方法论:
- 战略设计:通过限界上下文划分业务边界,通过上下文映射定义交互模式
- 战术设计:使用实体、值对象、聚合根、领域服务等模式构建领域模型
- CQRS:分离读写操作,分别优化性能
DDD的核心价值在于:让代码结构反映业务结构,降低沟通成本,提升系统可维护性。
延伸阅读
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。