Go 与领域驱动设计:构建复杂业务系统

系统讲解如何将领域驱动设计(DDD)的思想应用到 Go 项目中,覆盖实体、值对象、聚合、领域事件、仓储、应用服务、限界上下文、防腐层、CQRS 和事件溯源,配合整洁架构的完整实战

Go 与领域驱动设计:构建复杂业务系统

你有没有遇到过这样的代码:一个 UserService 里有 3000 行代码,混杂着数据库查询、业务规则校验、发邮件、记录日志……每次改一个小需求都要小心翼翼地测试整个链路。新同事入职看到这种代码,第一反应是"这谁写的?“第二反应是"我也不敢动”。

这种"大泥球"(Big Ball of Mud)架构之所以会出现,根本原因是我们把业务逻辑技术实现混在了一起。领域驱动设计(Domain-Driven Design,简称 DDD)就是为了解决这个问题而生的方法论。

今天,我们就来探讨如何将 DDD 的核心思想应用到 Go 项目中,构建一个真正能承载复杂业务的系统。

什么是 DDD?

领域驱动设计由 Eric Evans 在 2003 年提出,核心思想是:软件的设计应该围绕业务领域,而不是技术框架

DDD 分为两大部分:

战略设计(Strategic Design)

  • 限界上下文(Bounded Context):业务边界,比如"订单上下文"、“用户上下文”、“库存上下文”
  • 通用语言(Ubiquitous Language):开发和业务团队共用的语言
  • 上下文映射(Context Mapping):不同限界上下文之间的关系

战术设计(Tactical Design)

  • 实体(Entity):有唯一标识的对象,比如"订单"
  • 值对象(Value Object):没有唯一标识、按值比较的对象,比如"金额"
  • 聚合(Aggregate):一组相关对象作为修改单元,由聚合根管理
  • 仓储(Repository):封装持久化逻辑
  • 领域服务(Domain Service):不属于任何实体的业务逻辑
  • 应用服务(Application Service):协调领域对象完成用例
  • 领域事件(Domain Event):描述业务中发生的重要事件

项目案例:电商订单系统

让我们用一个电商订单系统来演示 DDD 的应用。这个系统要处理:

  • 订单创建、支付、发货、取消
  • 库存扣减
  • 用户账户
  • 促销活动
  • 通知发送

项目结构设计

DDD 项目的目录结构应该清晰反映业务边界:

eshop/
├── cmd/
│   └── api/
│       └── main.go                    # 应用入口
├── internal/
│   ├── order/                          # 订单限界上下文
│   │   ├── domain/                     # 领域层
│   │   │   ├── order.go                # 聚合根
│   │   │   ├── order_item.go           # 实体
│   │   │   ├── money.go                # 值对象
│   │   │   ├── address.go              # 值对象
│   │   │   ├── order_status.go         # 枚举
│   │   │   ├── events.go               # 领域事件
│   │   │   ├── repository.go           # 仓储接口
│   │   │   ├── errors.go               # 领域错误
│   │   │   └── service.go              # 领域服务
│   │   ├── application/                # 应用层
│   │   │   ├── command/                # 命令处理
│   │   │   ├── query/                  # 查询处理(CQRS)
│   │   │   ├── service.go              # 应用服务
│   │   │   └── dto/                    # 数据传输对象
│   │   ├── infrastructure/             # 基础设施层
│   │   │   ├── persistence/            # 持久化实现
│   │   │   ├── messaging/              # 消息队列
│   │   │   └── http/                   # HTTP 接口
│   │   └── interfaces/                 # 用户接口层
│   │       └── http/
│   │           ├── handler.go
│   │           └── middleware.go
│   ├── inventory/                      # 库存限界上下文
│   │   ├── domain/
│   │   ├── application/
│   │   └── infrastructure/
│   └── user/                           # 用户限界上下文
│       ├── domain/
│       ├── application/
│       └── infrastructure/
├── pkg/                                # 通用工具包
│   ├── events/                         # 事件总线
│   ├── errors/                         # 错误处理
│   └── middleware/
└── migrations/

注意每个限界上下文(order、inventory、user)都有自己的领域层、应用层和基础设施层。它们之间通过事件或防腐层通信,而不是直接耦合。

值对象(Value Object)

值对象是 DDD 中最基础但最容易被忽视的概念。它的特点:

  • 没有唯一标识,按值比较
  • 不可变(immutable)
  • 自包含业务规则

让我们从"金额"这个值对象开始:

// internal/order/domain/money.go
package domain

import (
	"errors"
	"fmt"
)

// Money 金额值对象
// 使用不可变设计,所有运算都返回新对象
type Money struct {
	amount   int64  // 以分为单位,避免浮点精度问题
	currency string // ISO 4217 货币代码
}

// NewMoney 创建金额
func NewMoney(amount int64, currency string) (Money, error) {
	if amount < 0 {
		return Money{}, errors.New("amount cannot be negative")
	}
	if !isValidCurrency(currency) {
		return Money{}, fmt.Errorf("invalid currency: %s", currency)
	}
	return Money{amount: amount, currency: currency}, nil
}

// Zero 零金额
func Zero(currency string) Money {
	return Money{amount: 0, currency: currency}
}

// Amount 获取金额(以分为单位)
func (m Money) Amount() int64 {
	return m.amount
}

// Currency 获取货币
func (m Money) Currency() string {
	return m.currency
}

// Add 加法
func (m Money) Add(other Money) (Money, error) {
	if m.currency != other.currency {
		return Money{}, fmt.Errorf("cannot add different currencies: %s and %s",
			m.currency, other.currency)
	}
	return Money{
		amount:   m.amount + other.amount,
		currency: m.currency,
	}, nil
}

// Subtract 减法
func (m Money) Subtract(other Money) (Money, error) {
	if m.currency != other.currency {
		return Money{}, fmt.Errorf("cannot subtract different currencies")
	}
	if m.amount < other.amount {
		return Money{}, errors.New("insufficient funds")
	}
	return Money{
		amount:   m.amount - other.amount,
		currency: m.currency,
	}, nil
}

// Multiply 乘法(用于计算折扣、税费等)
func (m Money) Multiply(multiplier float64) Money {
	// 注意浮点精度问题,生产环境建议使用 shopspring/decimal
	newAmount := int64(float64(m.amount) * multiplier)
	return Money{
		amount:   newAmount,
		currency: m.currency,
	}
}

// IsZero 是否为零
func (m Money) IsZero() bool {
	return m.amount == 0
}

// GreaterThan 是否大于另一个金额
func (m Money) GreaterThan(other Money) bool {
	if m.currency != other.currency {
		return false
	}
	return m.amount > other.amount
}

// Equals 是否相等(值对象按值比较)
func (m Money) Equals(other Money) bool {
	return m.amount == other.amount && m.currency == other.currency
}

// String 字符串表示
func (m Money) String() string {
	return fmt.Sprintf("%s %.2f", m.currency, float64(m.amount)/100)
}

// MarshalJSON 序列化为 JSON
func (m Money) MarshalJSON() ([]byte, error) {
	return []byte(fmt.Sprintf(`{"amount":%d,"currency":"%s"}`, m.amount, m.currency)), nil
}

func isValidCurrency(currency string) bool {
	validCurrencies := map[string]bool{
		"USD": true, "EUR": true, "CNY": true,
		"GBP": true, "JPY": true, "KRW": true,
	}
	return validCurrencies[currency]
}

另一个值对象——地址:

// internal/order/domain/address.go
package domain

import (
	"errors"
	"fmt"
	"strings"
)

// Address 地址值对象
type Address struct {
	country    string
	province   string
	city       string
	district   string
	street     string
	postalCode string
	recipient  string
	phone      string
}

// NewAddress 创建地址
func NewAddress(country, province, city, district, street, postalCode, recipient, phone string) (Address, error) {
	a := Address{
		country:    country,
		province:   province,
		city:       city,
		district:   district,
		street:     street,
		postalCode: postalCode,
		recipient:  recipient,
		phone:      phone,
	}

	if err := a.Validate(); err != nil {
		return Address{}, err
	}

	return a, nil
}

// Validate 验证地址
func (a Address) Validate() error {
	if strings.TrimSpace(a.country) == "" {
		return errors.New("country is required")
	}
	if strings.TrimSpace(a.city) == "" {
		return errors.New("city is required")
	}
	if strings.TrimSpace(a.street) == "" {
		return errors.New("street address is required")
	}
	if strings.TrimSpace(a.recipient) == "" {
		return errors.New("recipient is required")
	}
	if !isValidPhone(a.phone) {
		return errors.New("invalid phone number")
	}
	return nil
}

// Equals 值对象按值比较
func (a Address) Equals(other Address) bool {
	return a.country == other.country &&
		a.province == other.province &&
		a.city == other.city &&
		a.district == other.district &&
		a.street == other.street &&
		a.postalCode == other.postalCode &&
		a.recipient == other.recipient &&
		a.phone == other.phone
}

// FullAddress 返回完整地址字符串
func (a Address) FullAddress() string {
	parts := []string{a.country, a.province, a.city, a.district, a.street}
	return strings.Join(parts, " ")
}

// Getters...
func (a Address) Country() string    { return a.country }
func (a Address) Province() string   { return a.province }
func (a Address) City() string       { return a.city }
func (a Address) District() string   { return a.district }
func (a Address) Street() string     { return a.street }
func (a Address) PostalCode() string { return a.postalCode }
func (a Address) Recipient() string  { return a.recipient }
func (a Address) Phone() string      { return a.phone }

func isValidPhone(phone string) bool {
	// 简单验证,生产环境使用更严格的正则
	return len(phone) >= 8 && len(phone) <= 15
}

// String 实现 fmt.Stringer 接口
func (a Address) String() string {
	return fmt.Sprintf("%s, %s, %s", a.recipient, a.FullAddress(), a.phone)
}

值对象的威力在于:业务规则被封装在类型内部,任何使用 MoneyAddress 的地方都自动享有这些规则。你不可能把一个负数金额传给系统,也不可能创建一个没有收件人的地址。

实体(Entity)

实体和值对象不同,它有唯一标识,按标识比较。让我们设计订单的聚合根:

// internal/order/domain/order.go
package domain

import (
	"errors"
	"fmt"
	"time"
)

// OrderID 订单 ID(强类型标识)
type OrderID string

// Order 订单聚合根
// 聚合根是修改聚合内对象的唯一入口
type Order struct {
	id            OrderID
	userID        string
	items         []OrderItem           // 实体集合
	shippingAddr  Address
	totalAmount   Money
	status        OrderStatus
	paymentID     string
	cancelReason  string
	events        []DomainEvent         // 领域事件集合
	createdAt     time.Time
	updatedAt     time.Time
}

// NewOrder 创建新订单(工厂方法)
func NewOrder(id OrderID, userID string, items []OrderItem, shippingAddr Address, currency string) (*Order, error) {
	if len(items) == 0 {
		return nil, errors.New("order must have at least one item")
	}

	// 计算总金额
	total := Zero(currency)
	for _, item := range items {
		itemTotal, err := item.Subtotal()
		if err != nil {
			return nil, err
		}
		total, err = total.Add(itemTotal)
		if err != nil {
			return nil, err
		}
	}

	now := time.Now()
	order := &Order{
		id:           id,
		userID:       userID,
		items:        items,
		shippingAddr: shippingAddr,
		totalAmount:  total,
		status:       StatusPending,
		events:       []DomainEvent{},
		createdAt:    now,
		updatedAt:    now,
	}

	// 发布领域事件
	order.addEvent(&OrderCreatedEvent{
		OrderID:     string(id),
		UserID:      userID,
		TotalAmount: total,
		Items:       items,
		CreatedAt:   now,
	})

	return order, nil
}

// ID 获取订单 ID
func (o *Order) ID() OrderID {
	return o.id
}

// UserID 获取用户 ID
func (o *Order) UserID() string {
	return o.userID
}

// Items 获取订单项(返回副本,防止外部修改)
func (o *Order) Items() []OrderItem {
	result := make([]OrderItem, len(o.items))
	copy(result, o.items)
	return result
}

// TotalAmount 获取总金额
func (o *Order) TotalAmount() Money {
	return o.totalAmount
}

// Status 获取订单状态
func (o *Order) Status() OrderStatus {
	return o.status
}

// ShippingAddress 获取收货地址
func (o *Order) ShippingAddress() Address {
	return o.shippingAddr
}

// ConfirmPayment 确认支付
// 这是一个业务操作,包含业务规则
func (o *Order) ConfirmPayment(paymentID string) error {
	// 业务规则:只有待支付订单可以确认
	if o.status != StatusPending {
		return fmt.Errorf("cannot confirm payment for order in status %s", o.status)
	}

	if paymentID == "" {
		return errors.New("payment ID is required")
	}

	o.status = StatusPaid
	o.paymentID = paymentID
	o.updatedAt = time.Now()

	// 发布领域事件
	o.addEvent(&OrderPaidEvent{
		OrderID:   string(o.id),
		PaymentID: paymentID,
		Amount:    o.totalAmount,
		PaidAt:    o.updatedAt,
	})

	return nil
}

// Ship 发货
func (o *Order) Ship(trackingNumber string) error {
	if o.status != StatusPaid {
		return fmt.Errorf("cannot ship order in status %s, must be paid first", o.status)
	}

	if trackingNumber == "" {
		return errors.New("tracking number is required")
	}

	o.status = StatusShipped
	o.updatedAt = time.Now()

	o.addEvent(&OrderShippedEvent{
		OrderID:        string(o.id),
		TrackingNumber: trackingNumber,
		ShippedAt:      o.updatedAt,
	})

	return nil
}

// Cancel 取消订单
func (o *Order) Cancel(reason string) error {
	// 业务规则:已发货的订单不能取消
	if o.status == StatusShipped || o.status == StatusDelivered {
		return fmt.Errorf("cannot cancel order in status %s", o.status)
	}

	if reason == "" {
		return errors.New("cancel reason is required")
	}

	oldStatus := o.status
	o.status = StatusCancelled
	o.cancelReason = reason
	o.updatedAt = time.Now()

	o.addEvent(&OrderCancelledEvent{
		OrderID:    string(o.id),
		Reason:     reason,
		OldStatus:  string(oldStatus),
		CancelledAt: o.updatedAt,
	})

	return nil
}

// AddItem 添加商品项
func (o *Order) AddItem(item OrderItem) error {
	// 业务规则:只有待支付的订单可以添加商品
	if o.status != StatusPending {
		return fmt.Errorf("cannot add items to order in status %s", o.status)
	}

	// 检查是否已有相同商品
	for _, existing := range o.items {
		if existing.ProductID() == item.ProductID() {
			return fmt.Errorf("product %s already in order, use UpdateItemQuantity instead",
				item.ProductID())
		}
	}

	o.items = append(o.items, item)

	// 重新计算总金额
	if err := o.recalculateTotal(); err != nil {
		return err
	}

	o.updatedAt = time.Now()
	return nil
}

// UpdateItemQuantity 更新商品数量
func (o *Order) UpdateItemQuantity(productID string, newQuantity int) error {
	if o.status != StatusPending {
		return fmt.Errorf("cannot update items in order with status %s", o.status)
	}

	for i, item := range o.items {
		if item.ProductID() == productID {
			if err := o.items[i].SetQuantity(newQuantity); err != nil {
				return err
			}
			if err := o.recalculateTotal(); err != nil {
				return err
			}
			o.updatedAt = time.Now()
			return nil
		}
	}

	return fmt.Errorf("product %s not found in order", productID)
}

// recalculateTotal 重新计算总金额(私有方法)
func (o *Order) recalculateTotal() error {
	total := Zero(o.totalAmount.Currency())
	for _, item := range o.items {
		subtotal, err := item.Subtotal()
		if err != nil {
			return err
		}
		total, err = total.Add(subtotal)
		if err != nil {
			return err
		}
	}
	o.totalAmount = total
	return nil
}

// Events 获取并发布领域事件
func (o *Order) Events() []DomainEvent {
	events := o.events
	o.events = nil // 发布后清空
	return events
}

// addEvent 添加领域事件(私有方法)
func (o *Order) addEvent(event DomainEvent) {
	o.events = append(o.events, event)
}

// Equals 实体按标识比较
func (o *Order) Equals(other *Order) bool {
	if other == nil {
		return false
	}
	return o.id == other.id
}

订单项(实体)

OrderItem 是 Order 聚合内的实体。它有自己的标识,但只能通过 Order 来修改:

// internal/order/domain/order_item.go
package domain

import (
	"errors"
	"fmt"
)

// OrderItemID 订单项 ID
type OrderItemID string

// OrderItem 订单项(实体)
type OrderItem struct {
	id        OrderItemID
	productID string
	name      string
	price     Money
	quantity  int
}

// NewOrderItem 创建订单项
func NewOrderItem(id OrderItemID, productID, name string, price Money, quantity int) (OrderItem, error) {
	if productID == "" {
		return OrderItem{}, errors.New("product ID is required")
	}
	if name == "" {
		return OrderItem{}, errors.New("product name is required")
	}
	if quantity <= 0 {
		return OrderItem{}, errors.New("quantity must be greater than zero")
	}

	return OrderItem{
		id:        id,
		productID: productID,
		name:      name,
		price:     price,
		quantity:  quantity,
	}, nil
}

// ID 获取 ID
func (i OrderItem) ID() OrderItemID {
	return i.id
}

// ProductID 获取商品 ID
func (i OrderItem) ProductID() string {
	return i.productID
}

// Name 获取商品名
func (i OrderItem) Name() string {
	return i.name
}

// Price 获取单价
func (i OrderItem) Price() Money {
	return i.price
}

// Quantity 获取数量
func (i OrderItem) Quantity() int {
	return i.quantity
}

// Subtotal 计算小计金额
func (i OrderItem) Subtotal() (Money, error) {
	return i.price.Multiply(float64(i.quantity)), nil
}

// SetQuantity 设置数量(只允许 Order 聚合内部调用)
// 注意:Go 没有访问修饰符,我们通过约定和接口来控制
func (i *OrderItem) SetQuantity(qty int) error {
	if qty <= 0 {
		return fmt.Errorf("quantity must be greater than zero, got %d", qty)
	}
	if qty > 1000 {
		return errors.New("quantity cannot exceed 1000")
	}
	i.quantity = qty
	return nil
}

// Equals 实体按标识比较
func (i OrderItem) Equals(other OrderItem) bool {
	return i.id == other.id
}

领域事件(Domain Event)

领域事件描述业务中发生的重要事情。它让限界上下文之间可以松耦合地通信:

// internal/order/domain/events.go
package domain

import (
	"time"

	"github.com/google/uuid"
)

// DomainEvent 领域事件接口
type DomainEvent interface {
	EventID() string
	EventName() string
	OccurredAt() time.Time
}

// BaseEvent 基础事件实现
type BaseEvent struct {
	id         string
	name       string
	occurredAt time.Time
}

func NewBaseEvent(name string) BaseEvent {
	return BaseEvent{
		id:         uuid.New().String(),
		name:       name,
		occurredAt: time.Now(),
	}
}

func (e BaseEvent) EventID() string      { return e.id }
func (e BaseEvent) EventName() string    { return e.name }
func (e BaseEvent) OccurredAt() time.Time { return e.occurredAt }

// OrderCreatedEvent 订单创建事件
type OrderCreatedEvent struct {
	BaseEvent
	OrderID     string
	UserID      string
	TotalAmount Money
	Items       []OrderItem
	CreatedAt   time.Time
}

func NewOrderCreatedEvent(orderID, userID string, total Money, items []OrderItem) *OrderCreatedEvent {
	return &OrderCreatedEvent{
		BaseEvent:   NewBaseEvent("order.created"),
		OrderID:     orderID,
		UserID:      userID,
		TotalAmount: total,
		Items:       items,
		CreatedAt:   time.Now(),
	}
}

// OrderPaidEvent 订单支付事件
type OrderPaidEvent struct {
	BaseEvent
	OrderID   string
	PaymentID string
	Amount    Money
	PaidAt    time.Time
}

// OrderShippedEvent 订单发货事件
type OrderShippedEvent struct {
	BaseEvent
	OrderID        string
	TrackingNumber string
	ShippedAt      time.Time
}

// OrderCancelledEvent 订单取消事件
type OrderCancelledEvent struct {
	BaseEvent
	OrderID     string
	Reason      string
	OldStatus   string
	CancelledAt time.Time
}

// InventoryReservedEvent 库存预留事件(由库存上下文发出)
type InventoryReservedEvent struct {
	BaseEvent
	ReservationID string
	OrderID       string
	Items         []ReservedItem
}

// ReservedItem 预留商品项
type ReservedItem struct {
	ProductID string
	Quantity  int
}

// InventoryReservationFailedEvent 库存预留失败事件
type InventoryReservationFailedEvent struct {
	BaseEvent
	OrderID       string
	FailedProduct string
	Reason        string
}

事件总线(Event Bus)

为了让限界上下文之间通过事件通信,我们需要一个事件总线:

// pkg/events/bus.go
package events

import (
	"context"
	"sync"
)

// Event 通用事件接口
type Event interface {
	EventName() string
}

// Handler 事件处理器
type Handler func(ctx context.Context, event Event) error

// Bus 事件总线
type Bus interface {
	Publish(ctx context.Context, event Event) error
	Subscribe(eventName string, handler Handler)
}

// InMemoryBus 内存事件总线(开发和测试用)
type InMemoryBus struct {
	mu       sync.RWMutex
	handlers map[string][]Handler
}

func NewInMemoryBus() *InMemoryBus {
	return &InMemoryBus{
		handlers: make(map[string][]Handler),
	}
}

func (b *InMemoryBus) Publish(ctx context.Context, event Event) error {
	b.mu.RLock()
	handlers, ok := b.handlers[event.EventName()]
	b.mu.RUnlock()

	if !ok {
		return nil
	}

	// 异步执行所有处理器
	var wg sync.WaitGroup
	for _, handler := range handlers {
		wg.Add(1)
		go func(h Handler) {
			defer wg.Done()
			_ = h(ctx, event) // 生产环境应该处理错误
		}(handler)
	}
	wg.Wait()

	return nil
}

func (b *InMemoryBus) Subscribe(eventName string, handler Handler) {
	b.mu.Lock()
	defer b.mu.Unlock()
	b.handlers[eventName] = append(b.handlers[eventName], handler)
}

// Dispatch 从聚合根中发布所有领域事件
func Dispatch(ctx context.Context, bus Bus, events []Event) error {
	for _, event := range events {
		if err := bus.Publish(ctx, event); err != nil {
			return err
		}
	}
	return nil
}

生产环境通常会使用 RabbitMQ、Kafka 或 NATS 作为事件总线。

仓储接口(Repository)

仓储模式是 DDD 中最核心的抽象之一。接口定义在领域层,实现在基础设施层

// internal/order/domain/repository.go
package domain

import (
	"context"
)

// OrderRepository 订单仓储接口
// 注意:接口定义在领域层,由基础设施层实现
// 这样领域层不依赖任何持久化技术
type OrderRepository interface {
	// Save 保存订单(插入或更新)
	Save(ctx context.Context, order *Order) error

	// GetByID 根据 ID 获取订单
	GetByID(ctx context.Context, id OrderID) (*Order, error)

	// GetByUserID 根据用户 ID 查询订单
	GetByUserID(ctx context.Context, userID string, offset, limit int) ([]*Order, error)

	// GetByStatus 根据状态查询订单
	GetByStatus(ctx context.Context, status OrderStatus) ([]*Order, error)

	// NextID 生成新的订单 ID
	NextID(ctx context.Context) (OrderID, error)
}

// UnitOfWork 工作单元(保证聚合的事务完整性)
type UnitOfWork interface {
	// Execute 在事务中执行操作
	Execute(ctx context.Context, fn func(ctx context.Context) error) error
}

仓储实现位于基础设施层,可以使用 PostgreSQL、MongoDB 等任何存储:

// internal/order/infrastructure/persistence/postgres_order_repo.go
package persistence

import (
	"context"
	"database/sql"
	"encoding/json"
	"errors"
	"fmt"
	"time"

	"github.com/google/uuid"

	"github.com/yourusername/eshop/internal/order/domain"
)

// postgresOrderRepo 基于 PostgreSQL 的订单仓储实现
type postgresOrderRepo struct {
	db *sql.DB
}

// NewPostgresOrderRepository 创建仓储
func NewPostgresOrderRepository(db *sql.DB) domain.OrderRepository {
	return &postgresOrderRepo{db: db}
}

func (r *postgresOrderRepo) Save(ctx context.Context, order *domain.Order) error {
	// 序列化聚合根为 JSON(或使用多表存储)
	itemsJSON, err := json.Marshal(order.Items())
	if err != nil {
		return fmt.Errorf("failed to marshal items: %w", err)
	}

	addrJSON, err := json.Marshal(order.ShippingAddress())
	if err != nil {
		return fmt.Errorf("failed to marshal address: %w", err)
	}

	query := `
		INSERT INTO orders (id, user_id, items, shipping_address, total_amount, currency, status, payment_id, cancel_reason, created_at, updated_at)
		VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
		ON CONFLICT (id) DO UPDATE SET
			items = EXCLUDED.items,
			total_amount = EXCLUDED.total_amount,
			status = EXCLUDED.status,
			payment_id = EXCLUDED.payment_id,
			cancel_reason = EXCLUDED.cancel_reason,
			updated_at = EXCLUDED.updated_at
	`

	_, err = r.db.ExecContext(ctx, query,
		order.ID(),
		order.UserID(),
		itemsJSON,
		addrJSON,
		order.TotalAmount().Amount(),
		order.TotalAmount().Currency(),
		order.Status(),
		order.PaymentID(),
		order.CancelReason(),
		order.CreatedAt(),
		order.UpdatedAt(),
	)

	return err
}

func (r *postgresOrderRepo) GetByID(ctx context.Context, id domain.OrderID) (*domain.Order, error) {
	var (
		userID        string
		itemsJSON     []byte
		addrJSON      []byte
		amount        int64
		currency      string
		status        string
		paymentID     sql.NullString
		cancelReason  sql.NullString
		createdAt     time.Time
		updatedAt     time.Time
	)

	query := `
		SELECT user_id, items, shipping_address, total_amount, currency, status, payment_id, cancel_reason, created_at, updated_at
		FROM orders
		WHERE id = $1
	`

	err := r.db.QueryRowContext(ctx, query, id).Scan(
		&userID, &itemsJSON, &addrJSON, &amount, &currency, &status,
		&paymentID, &cancelReason, &createdAt, &updatedAt,
	)

	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return nil, fmt.Errorf("order %s not found: %w", id, err)
		}
		return nil, err
	}

	// 反序列化
	var items []domain.OrderItem
	if err := json.Unmarshal(itemsJSON, &items); err != nil {
		return nil, err
	}

	var addr domain.Address
	if err := json.Unmarshal(addrJSON, &addr); err != nil {
		return nil, err
	}

	total, err := domain.NewMoney(amount, currency)
	if err != nil {
		return nil, err
	}

	// 重建聚合根(使用工厂方法)
	order := domain.ReconstructOrder(
		id, userID, items, addr, total,
		domain.OrderStatus(status),
		paymentID.String,
		cancelReason.String,
		createdAt, updatedAt,
	)

	return order, nil
}

func (r *postgresOrderRepo) NextID(ctx context.Context) (domain.OrderID, error) {
	return domain.OrderID(uuid.New().String()), nil
}

// ... 其他方法实现 ...

为了让领域层可以"重建"聚合根(从数据库读取时),我们需要一个特殊的重构方法:

// ReconstructOrder 重构订单(从数据库读取时使用)
// 注意:这个方法不会触发领域事件,因为它只是恢复既有状态
func ReconstructOrder(
	id OrderID,
	userID string,
	items []OrderItem,
	shippingAddr Address,
	totalAmount Money,
	status OrderStatus,
	paymentID, cancelReason string,
	createdAt, updatedAt time.Time,
) *Order {
	return &Order{
		id:           id,
		userID:       userID,
		items:        items,
		shippingAddr: shippingAddr,
		totalAmount:  totalAmount,
		status:       status,
		paymentID:    paymentID,
		cancelReason: cancelReason,
		events:       []DomainEvent{}, // 重构时不触发事件
		createdAt:    createdAt,
		updatedAt:    updatedAt,
	}
}

应用服务(Application Service)

应用服务是领域层的"指挥官",负责:

  • 编排领域对象完成用例
  • 管理事务
  • 发布领域事件
  • 协调多个限界上下文
// internal/order/application/service.go
package application

import (
	"context"
	"errors"
	"fmt"

	"github.com/yourusername/eshop/internal/order/application/dto"
	"github.com/yourusername/eshop/internal/order/domain"
	"github.com/yourusername/eshop/pkg/events"
)

// OrderService 订单应用服务
// 注意:应用服务只做"薄薄的一层",不包含业务规则
type OrderService struct {
	orderRepo     domain.OrderRepository
	inventoryClient InventoryClient    // 防腐层(调用库存上下文)
	eventBus      events.Bus
	uow           domain.UnitOfWork
}

// NewOrderService 创建应用服务
func NewOrderService(
	orderRepo domain.OrderRepository,
	inventoryClient InventoryClient,
	eventBus events.Bus,
	uow domain.UnitOfWork,
) *OrderService {
	return &OrderService{
		orderRepo:       orderRepo,
		inventoryClient: inventoryClient,
		eventBus:        eventBus,
		uow:             uow,
	}
}

// PlaceOrder 下单(用例)
func (s *OrderService) PlaceOrder(ctx context.Context, req *dto.PlaceOrderRequest) (*dto.OrderResponse, error) {
	// 1. 生成订单 ID
	orderID, err := s.orderRepo.NextID(ctx)
	if err != nil {
		return nil, fmt.Errorf("failed to generate order ID: %w", err)
	}

	// 2. 构建订单项
	var items []domain.OrderItem
	for i, item := range req.Items {
		// 调用库存服务(通过防腐层)获取商品信息和价格
		productInfo, err := s.inventoryClient.GetProductInfo(ctx, item.ProductID)
		if err != nil {
			return nil, fmt.Errorf("failed to get product info: %w", err)
		}

		price, err := domain.NewMoney(productInfo.PriceInCents, productInfo.Currency)
		if err != nil {
			return nil, err
		}

		orderItem, err := domain.NewOrderItem(
			domain.OrderItemID(fmt.Sprintf("%s-%d", orderID, i)),
			item.ProductID,
			productInfo.Name,
			price,
			item.Quantity,
		)
		if err != nil {
			return nil, err
		}

		items = append(items, orderItem)
	}

	// 3. 构建收货地址
	addr, err := domain.NewAddress(
		req.ShippingAddress.Country,
		req.ShippingAddress.Province,
		req.ShippingAddress.City,
		req.ShippingAddress.District,
		req.ShippingAddress.Street,
		req.ShippingAddress.PostalCode,
		req.ShippingAddress.Recipient,
		req.ShippingAddress.Phone,
	)
	if err != nil {
		return nil, fmt.Errorf("invalid shipping address: %w", err)
	}

	// 4. 创建订单(领域对象负责业务规则)
	order, err := domain.NewOrder(orderID, req.UserID, items, addr, "CNY")
	if err != nil {
		return nil, fmt.Errorf("failed to create order: %w", err)
	}

	// 5. 预留库存(通过防腐层调用库存上下文)
	reservationID, err := s.inventoryClient.ReserveStock(ctx, req.UserID, string(orderID), items)
	if err != nil {
		return nil, fmt.Errorf("failed to reserve inventory: %w", err)
	}

	// 6. 在事务中保存订单并发布事件
	err = s.uow.Execute(ctx, func(ctx context.Context) error {
		if err := s.orderRepo.Save(ctx, order); err != nil {
			return err
		}

		// 发布领域事件
		domainEvents := order.Events()
		for _, event := range domainEvents {
			if err := s.eventBus.Publish(ctx, event); err != nil {
				return err
			}
		}

		return nil
	})
	if err != nil {
		// 补偿操作:释放库存
		_ = s.inventoryClient.ReleaseReservation(ctx, reservationID)
		return nil, fmt.Errorf("failed to save order: %w", err)
	}

	// 7. 返回 DTO
	return dto.FromOrder(order), nil
}

// ConfirmPayment 确认支付(用例)
func (s *OrderService) ConfirmPayment(ctx context.Context, orderID string, paymentID string) error {
	// 1. 加载聚合根
	id := domain.OrderID(orderID)
	order, err := s.orderRepo.GetByID(ctx, id)
	if err != nil {
		return err
	}

	// 2. 调用领域方法(业务规则在聚合根内部)
	if err := order.ConfirmPayment(paymentID); err != nil {
		return err
	}

	// 3. 在事务中保存并发布事件
	return s.uow.Execute(ctx, func(ctx context.Context) error {
		if err := s.orderRepo.Save(ctx, order); err != nil {
			return err
		}

		for _, event := range order.Events() {
			if err := s.eventBus.Publish(ctx, event); err != nil {
				return err
			}
		}

		return nil
	})
}

// CancelOrder 取消订单
func (s *OrderService) CancelOrder(ctx context.Context, orderID, reason string) error {
	id := domain.OrderID(orderID)
	order, err := s.orderRepo.GetByID(ctx, id)
	if err != nil {
		return err
	}

	if err := order.Cancel(reason); err != nil {
		return err
	}

	return s.uow.Execute(ctx, func(ctx context.Context) error {
		if err := s.orderRepo.Save(ctx, order); err != nil {
			return err
		}

		for _, event := range order.Events() {
			if err := s.eventBus.Publish(ctx, event); err != nil {
				return err
			}
		}

		return nil
	})
}

防腐层(Anti-Corruption Layer)

当两个限界上下文需要通信时,防腐层可以保护你的领域模型不受外部模型污染:

// internal/order/application/inventory_client.go
package application

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"

	"github.com/yourusername/eshop/internal/order/domain"
)

// InventoryClient 库存客户端接口(防腐层)
// 这个接口是用"订单上下文"的语言定义的,不包含任何库存上下文的细节
type InventoryClient interface {
	GetProductInfo(ctx context.Context, productID string) (*ProductInfo, error)
	ReserveStock(ctx context.Context, userID, orderID string, items []domain.OrderItem) (string, error)
	ReleaseReservation(ctx context.Context, reservationID string) error
}

// ProductInfo 商品信息(订单上下文视角)
type ProductInfo struct {
	ProductID    string
	Name         string
	PriceInCents int64
	Currency     string
	InStock      bool
}

// httpInventoryClient 基于 HTTP 的库存客户端实现
// 它把库存上下文的 API 转换为订单上下文的模型
type httpInventoryClient struct {
	baseURL    string
	httpClient *http.Client
}

func NewHTTPInventoryClient(baseURL string) InventoryClient {
	return &httpInventoryClient{
		baseURL:    baseURL,
		httpClient: &http.Client{},
	}
}

func (c *httpInventoryClient) GetProductInfo(ctx context.Context, productID string) (*ProductInfo, error) {
	req, err := http.NewRequestWithContext(ctx, "GET",
		fmt.Sprintf("%s/api/v1/products/%s", c.baseURL, productID), nil)
	if err != nil {
		return nil, err
	}

	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("inventory service returned status %d", resp.StatusCode)
	}

	// 解析库存上下文的响应(可能是完全不同的结构)
	var inventoryResp struct {
		SKU          string  `json:"sku"`
		DisplayName  string  `json:"display_name"`
		Price        float64 `json:"price"`
		CurrencyCode string  `json:"currency_code"`
		StockCount   int     `json:"stock_count"`
	}

	if err := json.NewDecoder(resp.Body).Decode(&inventoryResp); err != nil {
		return nil, err
	}

	// 转换为订单上下文的模型
	return &ProductInfo{
		ProductID:    inventoryResp.SKU,
		Name:         inventoryResp.DisplayName,
		PriceInCents: int64(inventoryResp.Price * 100),
		Currency:     inventoryResp.CurrencyCode,
		InStock:      inventoryResp.StockCount > 0,
	}, nil
}

func (c *httpInventoryClient) ReserveStock(ctx context.Context, userID, orderID string, items []domain.OrderItem) (string, error) {
	// 构建库存上下文的请求格式
	type reserveItem struct {
		SKU      string `json:"sku"`
		Quantity int    `json:"quantity"`
	}

	var reqItems []reserveItem
	for _, item := range items {
		reqItems = append(reqItems, reserveItem{
			SKU:      item.ProductID(),
			Quantity: item.Quantity(),
		})
	}

	reqBody := struct {
		UserID  string        `json:"user_id"`
		OrderID string        `json:"order_id"`
		Items   []reserveItem `json:"items"`
	}{
		UserID:  userID,
		OrderID: orderID,
		Items:   reqItems,
	}

	// ... 发送请求、解析响应、转换为订单上下文的 reservation ID ...

	return "res_" + orderID, nil // 简化
}

func (c *httpInventoryClient) ReleaseReservation(ctx context.Context, reservationID string) error {
	// ... 调用库存上下文 API 释放预留 ...
	return nil
}

防腐层的关键价值:即使库存上下文的 API 大改,订单上下文也只需要修改防腐层的实现代码,领域模型完全不受影响

领域服务(Domain Service)

某些业务逻辑不属于任何实体,比如跨聚合的计算,这时可以用领域服务:

// internal/order/domain/service.go
package domain

import (
	"context"
	"errors"
)

// PricingService 定价领域服务
// 负责复杂的定价逻辑(折扣、税费、运费等)
type PricingService struct {
	discountRepo DiscountRepository
	taxRepo      TaxRepository
}

// DiscountRepository 折扣仓储
type DiscountRepository interface {
	GetActiveDiscounts(ctx context.Context, userID string, productIDs []string) ([]Discount, error)
}

// TaxRepository 税率仓储
type TaxRepository interface {
	GetTaxRate(ctx context.Context, country string) (float64, error)
}

// Discount 折扣
type Discount struct {
	ID          string
	Code        string
	PercentOff  float64
	MaxDiscount Money
}

// NewPricingService 创建定价服务
func NewPricingService(discountRepo DiscountRepository, taxRepo TaxRepository) *PricingService {
	return &PricingService{
		discountRepo: discountRepo,
		taxRepo:      taxRepo,
	}
}

// CalculateFinalAmount 计算最终金额
func (s *PricingService) CalculateFinalAmount(ctx context.Context, order *Order, userID string) (Money, error) {
	// 1. 获取商品 ID 列表
	productIDs := make([]string, 0, len(order.Items()))
	for _, item := range order.Items() {
		productIDs = append(productIDs, item.ProductID())
	}

	// 2. 应用折扣
	discounts, err := s.discountRepo.GetActiveDiscounts(ctx, userID, productIDs)
	if err != nil {
		return Money{}, err
	}

	subtotal := order.TotalAmount()
	discountAmount := Zero(subtotal.Currency())

	for _, discount := range discounts {
		// 计算折扣金额
		discounted := subtotal.Multiply(discount.PercentOff / 100)
		// 不超过最大折扣
		if discount.MaxDiscount.GreaterThan(Zero(discount.MaxDiscount.Currency())) {
			if discounted.GreaterThan(discount.MaxDiscount) {
				discounted = discount.MaxDiscount
			}
		}
		discountAmount, _ = discountAmount.Add(discounted)
	}

	afterDiscount, err := subtotal.Subtract(discountAmount)
	if err != nil {
		return Money{}, err
	}

	// 3. 应用税费
	shippingCountry := order.ShippingAddress().Country()
	taxRate, err := s.taxRepo.GetTaxRate(ctx, shippingCountry)
	if err != nil {
		return Money{}, err
	}

	taxAmount := afterDiscount.Multiply(taxRate / 100)
	finalAmount, err := afterDiscount.Add(taxAmount)
	if err != nil {
		return Money{}, err
	}

	return finalAmount, nil
}

// ShippingCostCalculator 运费计算领域服务
type ShippingCostCalculator struct {
	rules []ShippingRule
}

// ShippingRule 运费规则
type ShippingRule struct {
	MinAmount    Money
	MaxAmount    Money
	Cost         Money
	FreeShipping bool
}

func (c *ShippingCostCalculator) Calculate(amount Money, addr Address) (Money, error) {
	for _, rule := range c.rules {
		if amount.GreaterThan(rule.MinAmount) || amount.Equals(rule.MinAmount) {
			if rule.FreeShipping {
				return Zero(amount.Currency()), nil
			}
			return rule.Cost, nil
		}
	}
	return Money{}, errors.New("no shipping rule matched")
}

CQRS:命令与查询分离

CQRS(Command Query Responsibility Segregation)是一种将"写操作"和"读操作"分离的模式。写操作走聚合根保证一致性,读操作可以绕过聚合根直接查询:

// internal/order/application/command/place_order.go
package command

import (
	"context"

	"github.com/yourusername/eshop/internal/order/application/dto"
	"github.com/yourusername/eshop/internal/order/application"
)

// PlaceOrderCommand 下单命令
type PlaceOrderCommand struct {
	UserID          string
	Items           []dto.OrderItemRequest
	ShippingAddress dto.AddressRequest
}

// PlaceOrderHandler 下单命令处理器
type PlaceOrderHandler struct {
	service *application.OrderService
}

func NewPlaceOrderHandler(service *application.OrderService) *PlaceOrderHandler {
	return &PlaceOrderHandler{service: service}
}

func (h *PlaceOrderHandler) Handle(ctx context.Context, cmd *PlaceOrderCommand) (*dto.OrderResponse, error) {
	req := &dto.PlaceOrderRequest{
		UserID:          cmd.UserID,
		Items:           cmd.Items,
		ShippingAddress: cmd.ShippingAddress,
	}
	return h.service.PlaceOrder(ctx, req)
}
// internal/order/application/query/order_list.go
package query

import (
	"context"
	"database/sql"

	"github.com/yourusername/eshop/internal/order/application/dto"
)

// OrderListQuery 订单列表查询
type OrderListQuery struct {
	db *sql.DB
}

func NewOrderListQuery(db *sql.DB) *OrderListQuery {
	return &OrderListQuery{db: db}
}

// ListByUser 查询用户的订单列表
// 注意:这里直接查询数据库,绕过聚合根,以获得更好的读性能
func (q *OrderListQuery) ListByUser(ctx context.Context, userID string, page, pageSize int) (*dto.OrderListResponse, error) {
	offset := (page - 1) * pageSize

	query := `
		SELECT o.id, o.user_id, o.status, o.total_amount, o.currency, o.created_at,
		       COUNT(oi.id) as item_count
		FROM orders o
		LEFT JOIN order_items oi ON o.id = oi.order_id
		WHERE o.user_id = $1
		GROUP BY o.id
		ORDER BY o.created_at DESC
		LIMIT $2 OFFSET $3
	`

	rows, err := q.db.QueryContext(ctx, query, userID, pageSize, offset)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var orders []dto.OrderSummary
	for rows.Next() {
		var o dto.OrderSummary
		if err := rows.Scan(
			&o.ID, &o.UserID, &o.Status, &o.TotalAmount,
			&o.Currency, &o.CreatedAt, &o.ItemCount,
		); err != nil {
			return nil, err
		}
		orders = append(orders, o)
	}

	// 查询总数
	var total int64
	err = q.db.QueryRowContext(ctx,
		"SELECT COUNT(*) FROM orders WHERE user_id = $1", userID).Scan(&total)
	if err != nil {
		return nil, err
	}

	return &dto.OrderListResponse{
		Orders: orders,
		Total:  total,
		Page:   page,
	}, nil
}

CQRS 的优势在于:写模型保证业务一致性,读模型可以针对查询场景优化(比如使用 Elasticsearch、Redis 缓存等)。

事件溯源(Event Sourcing)

事件溯源是一种特殊的持久化方式:不存储对象的当前状态,而是存储所有的事件,通过重放事件来重建对象。

// internal/order/domain/event_store.go
package domain

import (
	"context"
	"time"
)

// StoredEvent 存储的事件
type StoredEvent struct {
	EventID     string
	AggregateID string
	EventType   string
	Payload     []byte
	Version     int
	OccurredAt  time.Time
}

// EventStore 事件存储接口
type EventStore interface {
	// SaveEvents 保存事件
	SaveEvents(ctx context.Context, aggregateID string, events []DomainEvent, expectedVersion int) error

	// LoadEvents 加载事件
	LoadEvents(ctx context.Context, aggregateID string) ([]StoredEvent, error)
}

// EventSourcedOrder 基于事件溯源的订单聚合根
type EventSourcedOrder struct {
	id            OrderID
	userID        string
	items         []OrderItem
	status        OrderStatus
	version       int
	uncommitted   []DomainEvent
}

// Load 从事件流重建聚合根
func (o *EventSourcedOrder) Load(events []StoredEvent) error {
	for _, stored := range events {
		event, err := DeserializeEvent(stored.EventType, stored.Payload)
		if err != nil {
			return err
		}
		o.apply(event)
		o.version++
	}
	return nil
}

// apply 应用事件到聚合根(私有方法)
func (o *EventSourcedOrder) apply(event DomainEvent) {
	switch e := event.(type) {
	case *OrderCreatedEvent:
		o.id = OrderID(e.OrderID)
		o.userID = e.UserID
		o.items = e.Items
		o.status = StatusPending

	case *OrderPaidEvent:
		o.status = StatusPaid

	case *OrderShippedEvent:
		o.status = StatusShipped

	case *OrderCancelledEvent:
		o.status = StatusCancelled
	}
}

// Record 记录新事件
func (o *EventSourcedOrder) Record(event DomainEvent) {
	o.apply(event)
	o.uncommitted = append(o.uncommitted, event)
}

// UncommittedEvents 获取未提交的事件
func (o *EventSourcedOrder) UncommittedEvents() []DomainEvent {
	events := o.uncommitted
	o.uncommitted = nil
	return events
}

// Version 获取当前版本
func (o *EventSourcedOrder) Version() int {
	return o.version
}

事件溯源的优势:

  • 完整的历史:可以回溯到任何历史状态
  • 审计日志:事件本身就是审计日志
  • 灵活的查询:可以通过投影(Projection)生成多种查询视图
  • 时间旅行:可以"重放"事件到某个时间点

HTTP 接口层

最后让我们看看如何在 HTTP 接口中使用应用服务:

// internal/order/interfaces/http/handler.go
package http

import (
	"encoding/json"
	"net/http"

	"github.com/go-chi/chi/v5"

	"github.com/yourusername/eshop/internal/order/application"
	"github.com/yourusername/eshop/internal/order/application/command"
	"github.com/yourusername/eshop/internal/order/application/dto"
	"github.com/yourusername/eshop/internal/order/application/query"
)

// OrderHandler 订单 HTTP 处理器
type OrderHandler struct {
	placeOrderHandler *command.PlaceOrderHandler
	orderService      *application.OrderService
	orderListQuery    *query.OrderListQuery
}

func NewOrderHandler(
	placeOrderHandler *command.PlaceOrderHandler,
	orderService *application.OrderService,
	orderListQuery *query.OrderListQuery,
) *OrderHandler {
	return &OrderHandler{
		placeOrderHandler: placeOrderHandler,
		orderService:      orderService,
		orderListQuery:    orderListQuery,
	}
}

// Routes 配置路由
func (h *OrderHandler) Routes() chi.Router {
	r := chi.NewRouter()

	r.Post("/", h.PlaceOrder)
	r.Get("/", h.ListOrders)
	r.Get("/{id}", h.GetOrder)
	r.Post("/{id}/pay", h.ConfirmPayment)
	r.Post("/{id}/cancel", h.CancelOrder)

	return r
}

// PlaceOrder 下单
func (h *OrderHandler) PlaceOrder(w http.ResponseWriter, r *http.Request) {
	var req dto.PlaceOrderRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	defer r.Body.Close()

	// 从认证中间件获取用户 ID
	userID := r.Context().Value("userID").(string)
	req.UserID = userID

	cmd := &command.PlaceOrderCommand{
		UserID:          req.UserID,
		Items:           req.Items,
		ShippingAddress: req.ShippingAddress,
	}

	resp, err := h.placeOrderHandler.Handle(r.Context(), cmd)
	if err != nil {
		writeError(w, err)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusCreated)
	json.NewEncoder(w).Encode(resp)
}

// ConfirmPayment 确认支付
func (h *OrderHandler) ConfirmPayment(w http.ResponseWriter, r *http.Request) {
	orderID := chi.URLParam(r, "id")

	var req struct {
		PaymentID string `json:"payment_id"`
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	if err := h.orderService.ConfirmPayment(r.Context(), orderID, req.PaymentID); err != nil {
		writeError(w, err)
		return
	}

	w.WriteHeader(http.StatusOK)
	w.Write([]byte(`{"status":"ok"}`))
}

// ListOrders 查询订单列表(走 CQRS 的查询侧)
func (h *OrderHandler) ListOrders(w http.ResponseWriter, r *http.Request) {
	userID := r.Context().Value("userID").(string)
	page := parseIntParam(r, "page", 1)
	pageSize := parseIntParam(r, "page_size", 20)

	resp, err := h.orderListQuery.ListByUser(r.Context(), userID, page, pageSize)
	if err != nil {
		writeError(w, err)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(resp)
}

// ... 其他处理器方法 ...

func writeError(w http.ResponseWriter, err error) {
	status := http.StatusInternalServerError
	message := "internal server error"

	// 根据错误类型返回合适的状态码
	switch err.Error() {
	case "order not found":
		status = http.StatusNotFound
	case "insufficient inventory":
		status = http.StatusConflict
	default:
		// 业务错误
		if isBusinessError(err) {
			status = http.StatusBadRequest
			message = err.Error()
		}
	}

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	json.NewEncoder(w).Encode(map[string]string{"error": message})
}

总结

恭喜你完成了领域驱动设计的学习之旅!让我们回顾核心要点:

  1. 战略设计:用限界上下文划分业务边界,用通用语言统一团队沟通
  2. 值对象:封装业务规则,不可变,按值比较(如 Money、Address)
  3. 实体与聚合根:实体有唯一标识,聚合根是修改聚合内对象的唯一入口
  4. 领域事件:描述业务中发生的重要事件,用于限界上下文之间的松耦合通信
  5. 仓储:接口定义在领域层,实现在基础设施层,实现依赖倒置
  6. 应用服务:薄薄的一层,只负责编排,不包含业务规则
  7. 防腐层:保护领域模型不受外部模型污染
  8. CQRS:命令和查询分离,写模型保证一致性,读模型优化性能
  9. 事件溯源:存储事件而非状态,可以重放事件重建历史

DDD 不是银弹,它适用于业务逻辑复杂的系统。对于简单的 CRUD 应用,DDD 反而会增加不必要的复杂度。但当你需要构建一个能持续演进、承载复杂业务规则的系统时,DDD 就是你的最佳武器。

Go 语言的简洁和强类型特性与 DDD 相得益彰——没有继承、没有反射魔法、没有复杂的注解,一切都清晰明了。这正是构建长期可维护系统所需要的。

希望这篇长达 96 篇的 Go 语言入门系列能帮助你从入门到精通。记住,编程之路没有终点,每一行代码都是一次新的探索。祝你编码愉快!

继续阅读

探索更多技术文章

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

全部文章 返回首页