领域建模参考《实现领域驱动设计》对聚合与实现边界的讨论。[1] 《领域驱动设计》提供统一语言与模型的概念基础;本文的电商代码是围绕这些概念组织的教学示例。[2]
复杂业务代码的”痛点画像” 为什么复杂业务的代码容易变烂? 在电商、金融、社交等复杂业务场景中,代码腐化几乎是必然趋势。根本原因包括:
需求频繁变更 :营销活动每周上新,代码不断打补丁
多人协作冲突 :10+ 开发者同时修改,缺乏统一规范
性能优化压力 :为了提升性能,牺牲代码可读性
历史包袱沉重 :不敢重构老代码,只能在上面继续堆砌
业务理解偏差 :产品、技术、运营对同一需求理解不一致
典型场景 :一个最初只有 100 行的下单函数,经过 2 年迭代后膨胀到 1500 行,包含 15 个 if-else 嵌套,8 个外部依赖调用,3 个数据库事务,无人敢动。
典型的”烂代码”症状 千行函数的噩梦 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 func CreateOrder (req *CreateOrderRequest) (*Order, error ) { if req == nil { return nil , errors.New("request is nil" ) } if req.UserID == 0 { return nil , errors.New("user_id is required" ) } if len (req.Items) == 0 { return nil , errors.New("items is required" ) } userResp, err := userService.GetUser(req.UserID) if err != nil { } for _, item := range req.Items { stock, err := inventoryService.CheckStock(item.ItemID) } var totalPrice int64 return order, nil }
问题分析 :
❌ 单个函数承担了 14 个职责
❌ 无法单元测试(依赖太多外部服务)
❌ 修改任何一个环节都可能影响其他环节
❌ 新人无法快速理解业务流程
❌ 代码复用率极低
if-else 地狱 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 func CalculatePrice (order *Order) (int64 , error ) { if order.Type == "normal" { if order.Region == "SG" { if order.UserLevel == "VIP" { if order.PaymentMethod == "credit_card" { if order.PromotionType == "flash_sale" { if order.ItemStock > 0 { return order.BasePrice * 0.5 , nil } else { return 0 , errors.New("out of stock" ) } } else if order.PromotionType == "bundle" { if order.BundleItemCount >= 3 { return order.BasePrice * 0.7 , nil } else { return order.BasePrice * 0.8 , nil } } else { return order.BasePrice * 0.9 , nil } } else if order.PaymentMethod == "ewallet" { return order.BasePrice * 0.95 , nil } else { return order.BasePrice, nil } } else if order.UserLevel == "SVIP" { } else { } } else if order.Region == "ID" { } else { } } else if order.Type == "topup" { } else if order.Type == "hotel" { } return 0 , errors.New("unsupported order type" ) }
问题分析 :
❌ 认知负担极高(需要记住 6 层条件)
❌ 圈复杂度爆炸(McCabe > 50)
❌ 新增条件需要修改现有代码(违反开闭原则)
❌ 测试用例数量 = 2^n(条件分支数)
上下文爆炸(参数传递链) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 func CalculatePrice ( itemID int64 , modelID int64 , userID int64 , regionID int64 , quantity int32 , isNewUser bool , useVoucher bool , useCoin bool , voucherCode string , coinAmount int64 , promotionIDs []int64 , shippingAddress *Address, paymentMethod string , platform string , deviceType string , ) (*Price, error ) { } price, err := CalculatePrice( 123 , 456 , 789 , 1 , 2 , true , true , false , "SAVE100" , 0 , []int64 {1 , 2 }, address, "credit_card" , "app" , "ios" , )
问题分析 :
❌ 调用方容易传错参数顺序
❌ 参数类型相似(多个 int64),编译器无法检查
❌ 新增参数需要修改所有调用方
❌ 参数之间可能有隐含的依赖关系(如 useVoucher=true 时必须传 voucherCode)
改一处动全身 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 1. order_service.go (订单创建逻辑)2. price_calculator.go (价格计算逻辑)3. voucher_service.go (优惠券服务)4. promotion_service.go (营销服务)5. payment_service.go (支付服务)6. refund_service.go (退款服务 - 逆向计算)7. order_dto.go (DTO 定义)8. order_test.go (单元测试)- 修改了 8 个文件 - 可能引入 3 -5 个新 Bug - 测试回归需要 2 天 - 不敢删除老代码,只能注释掉(留下大量"僵尸代码" )
根本原因 :
❌ 逻辑分散在多个服务
❌ 缺乏统一的抽象层
❌ 职责边界不清晰
❌ 依赖关系混乱
无法单元测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 func ProcessOrder (orderID int64 ) error { db := global.DB cache := global.Redis user, err := userService.GetUser(orderID) if err != nil { return err } paymentClient := payment.NewClient("http://payment-service" ) file, _ := os.Open("/var/log/order.log" ) defer file.Close() now := time.Now() orderNo := fmt.Sprintf("ORD%d" , rand.Int63()) return nil }
问题分析 :
❌ 依赖全局变量,无法 mock
❌ 依赖外部服务,测试需要真实环境
❌ 依赖文件系统,测试需要真实文件
❌ 依赖时间和随机数,结果不可预测
❌ 函数内部创建依赖,无法注入 mock 对象
代码腐化的根本原因 职责不清(违反单一职责原则) 1 2 3 4 5 6 7 8 9 10 11 type OrderService struct { }
耦合过高(模块间相互依赖) 1 2 3 4 OrderService → PriceService → PromotionService → ItemService → OrderService ↑ ↓ └───────────────────────────────────────────────┘ (循环依赖)
抽象缺失(直接调用底层实现) 1 2 3 4 5 6 func CreateOrderHandler (ctx *gin.Context) { order := &Order{...} db.Create(order) }
缺乏约束(没有统一规范)
每个人的错误处理方式不同
日志格式不统一
命名风格各异
没有 Code Review 流程
Clean Code 的判断标准 《Clean Code》的命名、函数和错误处理章节提供可读性与职责划分的基础。[3] 以下目标与示例是本文结合业务场景给出的评审建议。
可读性:代码即文档 目标 :新人在不依赖文档的情况下,能够快速理解代码逻辑。
命名清晰 1 2 3 4 5 6 7 8 9 10 11 var d int var list []int var flag bool var tmp string var daysSinceCreation int var activeUserIDs []int var isNewUser bool var tempOrderNumber string
结构简单 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func GetUserOrder (userID, orderID int64 ) (*Order, error ) { if err := validateUser(userID); err != nil { return nil , err } order, err := getOrder(orderID) if err != nil { return nil , err } if !canAccessOrder(userID, order) { return nil , ErrPermissionDenied } return order, nil }
注释恰当 1 2 3 4 5 6 7 8 9 10 11 12 13 userID := req.GetUserID() for i := 0 ; i < 3 ; i++ { if err := supplierAPI.Book(ctx, req); err == nil { break } time.Sleep(time.Second * time.Duration(i+1 )) }
可测试性:单元测试覆盖率 目标 :核心业务逻辑单元测试覆盖率 > 70%。
依赖可注入 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 type OrderService struct { userRepo UserRepository orderRepo OrderRepository paymentSvc PaymentService } func TestCreateOrder (t *testing.T) { mockUserRepo := &MockUserRepository{} mockOrderRepo := &MockOrderRepository{} mockPaymentSvc := &MockPaymentService{} service := NewOrderService(mockUserRepo, mockOrderRepo, mockPaymentSvc) order, err := service.CreateOrder(ctx, req) assert.NoError(t, err) assert.NotNil(t, order) }
职责单一 1 2 3 4 5 6 7 8 9 func ValidateOrder (order *Order) error { }func CalculatePrice (order *Order) (*Price, error ) { }func SaveOrder (order *Order) error { }func TestValidateOrder (t *testing.T) { }func TestCalculatePrice (t *testing.T) { }func TestSaveOrder (t *testing.T) { }
无副作用 1 2 3 4 5 6 7 8 9 10 func CalculateDiscount (basePrice int64 , discountRate float64 ) int64 { return int64 (float64 (basePrice) * discountRate) } func TestCalculateDiscount (t *testing.T) { assert.Equal(t, int64 (90 ), CalculateDiscount(100 , 0.9 )) assert.Equal(t, int64 (80 ), CalculateDiscount(100 , 0.8 )) }
可维护性:修改成本低 目标 :修改一个功能,平均只需要改动 1-2 个文件。
低耦合 1 2 3 4 5 6 7 8 9 10 11 type PriceCalculator interface { Calculate(ctx context.Context, order *Order) (*Price, error ) } type OrderService struct { calculator PriceCalculator }
高内聚 1 2 3 4 5 6 7 8 9 10 11 package pricingtype Calculator struct {}func (c *Calculator) CalculateBasePrice() {}func (c *Calculator) ApplyPromotions() {}func (c *Calculator) ApplyVoucher() {}func (c *Calculator) CalculateFinalPrice() {}
可追溯 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 func CreateOrder (ctx context.Context, req *Req) (*Order, error ) { logger.Infof("CreateOrder start, userID=%d, items=%v" , req.UserID, req.Items) logger.Debugf("Step1: validate request" ) if err := validateRequest(req); err != nil { logger.Errorf("validate failed: %v" , err) return nil , err } logger.Debugf("Step2: calculate price" ) price, err := calculatePrice(ctx, req) if err != nil { logger.Errorf("calculate price failed: %v" , err) return nil , err } logger.Infof("CreateOrder success, orderID=%s, price=%d" , order.ID, price.Final) return order, nil }
可扩展性:新增功能不改老代码 目标 :符合开闭原则(对扩展开放,对修改封闭)。
开闭原则 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 type PriceCalculator interface { Calculate(ctx context.Context, req *PriceRequest) (*Price, error ) } type TopupCalculator struct {} type HotelCalculator struct {} type FlightCalculator struct {} func GetCalculator (categoryID int64 ) PriceCalculator { switch categoryID { case CategoryTopup: return &TopupCalculator{} case CategoryHotel: return &HotelCalculator{} case CategoryFlight: return &FlightCalculator{} default : return &DefaultCalculator{} } }
插件化 1 2 3 4 5 6 7 8 9 10 pipeline := NewPipeline(). AddProcessor(NewValidationProcessor()). AddProcessor(NewPriceCalculator()). AddProcessor(NewInventoryChecker()). AddProcessor(NewRiskChecker()). AddProcessor(NewCacheProcessor())
配置驱动 1 2 3 4 5 6 7 8 9 10 11 features: risk_check: enabled: true threshold: 1000 cache: enabled: true ttl: 300s new_user_discount: enabled: true discount_rate: 0.8
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func ProcessOrder (order *Order) error { if config.Features.RiskCheck.Enabled { if err := riskCheck(order); err != nil { return err } } if config.Features.Cache.Enabled { } return nil }
核心设计原则 SOLID 原则在复杂业务中的应用 单一职责原则 (Single Responsibility Principle) 定义 :一个类/函数应该只有一个引起它变化的原因。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 func ProcessOrder (order *Order) error { if order.UserID == 0 { return errors.New("invalid user" ) } price := order.BasePrice * order.Quantity if err := reduceStock(order.ItemID, order.Quantity); err != nil { return err } if err := db.Create(order); err != nil { return err } sendNotification(order.UserID, "order_created" ) return nil } func ValidateOrder (order *Order) error { if order.UserID == 0 { return errors.New("invalid user" ) } return nil } func CalculatePrice (order *Order) (*Price, error ) { return &Price{ Total: order.BasePrice * order.Quantity, }, nil } func ReserveInventory (itemID int64 , quantity int32 ) error { return inventoryService.Reserve(itemID, quantity) } func SaveOrder (order *Order) error { return orderRepo.Create(order) } func NotifyUser (userID int64 , event string ) error { return notificationService.Send(userID, event) } func ProcessOrder (order *Order) error { if err := ValidateOrder(order); err != nil { return err } price, err := CalculatePrice(order) if err != nil { return err } order.Price = price if err := ReserveInventory(order.ItemID, order.Quantity); err != nil { return err } if err := SaveOrder(order); err != nil { ReleaseInventory(order.ItemID, order.Quantity) return err } NotifyUser(order.UserID, "order_created" ) return nil }
收益 :
✅ 每个函数职责清晰
✅ 可以独立测试每个函数
✅ 修改某个职责不影响其他职责
✅ 代码复用率高
开闭原则 (Open/Closed Principle) 定义 :软件实体应该对扩展开放,对修改封闭。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 func CalculatePrice (categoryID int64 , order *Order) (*Price, error ) { if categoryID == CategoryTopup { return calculateTopupPrice(order), nil } else if categoryID == CategoryHotel { return calculateHotelPrice(order), nil } else if categoryID == CategoryFlight { return calculateFlightPrice(order), nil } return nil , errors.New("unsupported category" ) } type PriceCalculator interface { Calculate(ctx context.Context, order *Order) (*Price, error ) Support(categoryID int64 ) bool } type TopupCalculator struct {}func (c *TopupCalculator) Calculate(ctx context.Context, order *Order) (*Price, error ) { return &Price{Total: order.FaceValue * 0.95 }, nil } func (c *TopupCalculator) Support(categoryID int64 ) bool { return categoryID == CategoryTopup } type HotelCalculator struct {}func (c *HotelCalculator) Calculate(ctx context.Context, order *Order) (*Price, error ) { return &Price{Total: order.RoomPrice * order.Nights}, nil } func (c *HotelCalculator) Support(categoryID int64 ) bool { return categoryID == CategoryHotel } type FlightCalculator struct {}func (c *FlightCalculator) Calculate(ctx context.Context, order *Order) (*Price, error ) { return &Price{Total: order.TicketPrice + order.Tax}, nil } func (c *FlightCalculator) Support(categoryID int64 ) bool { return categoryID == CategoryFlight } type CalculatorRegistry struct { calculators []PriceCalculator } func (r *CalculatorRegistry) Register(calculator PriceCalculator) { r.calculators = append (r.calculators, calculator) } func (r *CalculatorRegistry) GetCalculator(categoryID int64 ) PriceCalculator { for _, calc := range r.calculators { if calc.Support(categoryID) { return calc } } return nil } registry := &CalculatorRegistry{} registry.Register(&TopupCalculator{}) registry.Register(&HotelCalculator{}) registry.Register(&FlightCalculator{}) calculator := registry.GetCalculator(order.CategoryID) price, err := calculator.Calculate(ctx, order)
收益 :
✅ 新增品类不需要修改现有代码
✅ 每个计算器独立开发和测试
✅ 降低代码耦合度
✅ 支持动态注册(如插件机制)
里氏替换原则 (Liskov Substitution Principle) 定义 :子类应该能够替换父类并出现在父类能够出现的任何地方。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 type PaymentService interface { Pay(ctx context.Context, order *Order) (*PaymentResult, error ) Refund(ctx context.Context, paymentID string , amount int64 ) error } type CreditCardPayment struct {}func (p *CreditCardPayment) Pay(ctx context.Context, order *Order) (*PaymentResult, error ) { return &PaymentResult{PaymentID: "CC123" }, nil } func (p *CreditCardPayment) Refund(ctx context.Context, paymentID string , amount int64 ) error { return nil } type EWalletPayment struct {}func (p *EWalletPayment) Pay(ctx context.Context, order *Order) (*PaymentResult, error ) { return &PaymentResult{PaymentID: "EW456" }, nil } func (p *EWalletPayment) Refund(ctx context.Context, paymentID string , amount int64 ) error { return nil } func ProcessPayment (paymentService PaymentService, order *Order) error { result, err := paymentService.Pay(ctx, order) if err != nil { return err } order.PaymentID = result.PaymentID return nil } ProcessPayment(&CreditCardPayment{}, order) ProcessPayment(&EWalletPayment{}, order)
接口隔离原则 (Interface Segregation Principle) 定义 :客户端不应该依赖它不需要的接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 type OrderService interface { CreateOrder(ctx context.Context, req *Req) (*Order, error ) CancelOrder(ctx context.Context, orderID string ) error GetOrder(ctx context.Context, orderID string ) (*Order, error ) ListOrders(ctx context.Context, userID int64 ) ([]*Order, error ) UpdateShipping(ctx context.Context, orderID string , tracking string ) error CalculateRefund(ctx context.Context, orderID string ) (*Refund, error ) ProcessRefund(ctx context.Context, orderID string ) error } type OrderCreator interface { CreateOrder(ctx context.Context, req *Req) (*Order, error ) } type OrderCanceller interface { CancelOrder(ctx context.Context, orderID string ) error } type OrderReader interface { GetOrder(ctx context.Context, orderID string ) (*Order, error ) ListOrders(ctx context.Context, userID int64 ) ([]*Order, error ) } type OrderShipper interface { UpdateShipping(ctx context.Context, orderID string , tracking string ) error } type OrderRefunder interface { CalculateRefund(ctx context.Context, orderID string ) (*Refund, error ) ProcessRefund(ctx context.Context, orderID string ) error } type OrderDisplayService struct { reader OrderReader } type OrderCheckoutService struct { creator OrderCreator reader OrderReader }
依赖倒置原则 (Dependency Inversion Principle) 定义 :高层模块不应该依赖低层模块,两者都应该依赖抽象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 type OrderService struct { db *gorm.DB redis *redis.Client } func (s *OrderService) GetOrder(orderID string ) (*Order, error ) { var order Order if err := s.db.Where("id = ?" , orderID).First(&order).Error; err != nil { return nil , err } return &order, nil } type OrderRepository interface { GetByID(ctx context.Context, orderID string ) (*Order, error ) Save(ctx context.Context, order *Order) error Update(ctx context.Context, order *Order) error Delete(ctx context.Context, orderID string ) error } type OrderService struct { repo OrderRepository } func (s *OrderService) GetOrder(ctx context.Context, orderID string ) (*Order, error ) { return s.repo.GetByID(ctx, orderID) } type GormOrderRepository struct { db *gorm.DB } func (r *GormOrderRepository) GetByID(ctx context.Context, orderID string ) (*Order, error ) { var order Order if err := r.db.Where("id = ?" , orderID).First(&order).Error; err != nil { return nil , err } return &order, nil } type MongoOrderRepository struct { client *mongo.Client } func (r *MongoOrderRepository) GetByID(ctx context.Context, orderID string ) (*Order, error ) { return &Order{}, nil } type MockOrderRepository struct { orders map [string ]*Order } func (r *MockOrderRepository) GetByID(ctx context.Context, orderID string ) (*Order, error ) { if order, ok := r.orders[orderID]; ok { return order, nil } return nil , errors.New("order not found" ) } func TestGetOrder (t *testing.T) { mockRepo := &MockOrderRepository{ orders: map [string ]*Order{ "123" : {ID: "123" , UserID: 456 }, }, } service := &OrderService{repo: mockRepo} order, err := service.GetOrder(ctx, "123" ) assert.NoError(t, err) assert.Equal(t, "123" , order.ID) }
收益 :
✅ 高层模块(Service)不依赖低层模块(DB)的具体实现
✅ 可以轻松切换底层实现(GORM → MongoDB)
✅ 可以轻松进行单元测试(使用 Mock)
✅ 符合开闭原则
分层架构:职责清晰的代码组织 经典三层架构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 ┌─────────────────────────────────────────────────────┐ │ Controller Layer (控制层 / 接口层) │ │ ───────────────────────────────────────────────── │ │ 职责: │ │ • HTTP 请求/响应处理 │ │ • 参数校验和格式转换 │ │ • 调用 Service 层处理业务逻辑 │ │ • 统一错误处理和响应封装 │ │ │ │ 特点: │ │ • 薄薄的一层,不包含业务逻辑 │ │ • 负责协议转换(HTTP → 内部对象) │ │ • 处理框架相关的逻辑 │ └─────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────┐ │ Service Layer (服务层 / 业务层) │ │ ───────────────────────────────────────────────── │ │ 职责: │ │ • 业务逻辑编排 │ │ • 事务管理 │ │ • 异常处理 │ │ • 调用 Repository 层获取/保存数据 │ │ │ │ 特点: │ │ • 核心业务逻辑所在 │ │ • 可复用的业务能力 │ │ • 独立于具体的存储和通信协议 │ └─────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────┐ │ Repository Layer (数据访问层 / 持久化层) │ │ ───────────────────────────────────────────────── │ │ 职责: │ │ • 数据库操作(CRUD) │ │ • 缓存操作 │ │ • 外部服务调用 │ │ • 数据格式转换(DO ↔ PO) │ │ │ │ 特点: │ │ • 封装数据访问细节 │ │ • 对上层屏蔽具体的存储实现 │ │ • 可以独立切换存储方案 │ └─────────────────────────────────────────────────────┘
示例代码 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 type OrderController struct { orderService OrderService } func (c *OrderController) CreateOrder(ctx *gin.Context) { var req CreateOrderRequest if err := ctx.ShouldBindJSON(&req); err != nil { ctx.JSON(400 , gin.H{"error" : "invalid request" }) return } order, err := c.orderService.CreateOrder(ctx, &req) if err != nil { ctx.JSON(500 , gin.H{"error" : err.Error()}) return } ctx.JSON(200 , gin.H{"data" : order}) } type OrderService interface { CreateOrder(ctx context.Context, req *CreateOrderRequest) (*Order, error ) } type orderService struct { orderRepo OrderRepository inventoryRepo InventoryRepository priceService PriceService } func (s *orderService) CreateOrder(ctx context.Context, req *CreateOrderRequest) (*Order, error ) { if err := s.inventoryRepo.CheckStock(ctx, req.ItemID, req.Quantity); err != nil { return nil , fmt.Errorf("insufficient stock: %w" , err) } price, err := s.priceService.Calculate(ctx, req) if err != nil { return nil , fmt.Errorf("calculate price failed: %w" , err) } order := &Order{ ID: generateOrderID(), UserID: req.UserID, ItemID: req.ItemID, Quantity: req.Quantity, Price: price.Total, Status: "pending" , } if err := s.orderRepo.Save(ctx, order); err != nil { return nil , fmt.Errorf("save order failed: %w" , err) } return order, nil } type OrderRepository interface { Save(ctx context.Context, order *Order) error GetByID(ctx context.Context, orderID string ) (*Order, error ) Update(ctx context.Context, order *Order) error } type orderRepository struct { db *gorm.DB } func (r *orderRepository) Save(ctx context.Context, order *Order) error { return r.db.WithContext(ctx).Create(order).Error } func (r *orderRepository) GetByID(ctx context.Context, orderID string ) (*Order, error ) { var order Order if err := r.db.WithContext(ctx).Where("id = ?" , orderID).First(&order).Error; err != nil { return nil , err } return &order, nil } func (r *orderRepository) Update(ctx context.Context, order *Order) error { return r.db.WithContext(ctx).Save(order).Error }
三层架构的优势 :
✅ 职责清晰:每一层只关注自己的职责
✅ 易于测试:Service 层可以 mock Repository 进行测试
✅ 易于替换:可以轻松切换 Web 框架或数据库
✅ 易于理解:新人能快速找到代码位置
DDD 四层架构(参考 nsf-lotto 项目) DDD(Domain-Driven Design,领域驱动设计)在三层架构基础上,进一步强调领域模型 的重要性,并将基础设施与领域逻辑解耦。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 项目结构示例(参考 nsf-lotto): nsf-lotto/ ├── processor.go # 插件入口(Pipeline 处理器) ├── application/ # 应用层(Application Layer) │ ├── lottery_service.go │ ├── lottery_service_test.go │ └── converter.go # DTO 转换 ├── domain/ # 领域层(Domain Layer) │ ├── lottery.go # 领域模型/实体 │ ├── lottery_test.go │ └── repository.go # 仓储接口(DIP) └── infrastructure/ # 基础设施层(Infrastructure Layer) ├── lottery_repo.go # 仓储实现 ├── cache_repo.go # 缓存实现 ├── item_repo.go └── user_repo.go
各层职责详解 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 ┌─────────────────────────────────────────────────────┐ │ Processor Layer (处理器层 / 入口层) │ │ ───────────────────────────────────────────────── │ │ 职责: │ │ • Pipeline 入口 │ │ • 路由和流程编排 │ │ • 集成到框架(如 GAS Plugin) │ │ │ │ 示例:processor.go │ └─────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────┐ │ Application Layer (应用层) │ │ ───────────────────────────────────────────────── │ │ 职责: │ │ • 应用服务(Use Case 编排) │ │ • 协调领域对象完成业务用例 │ │ • DTO 转换(Domain Object ↔ DTO) │ │ • 事务控制 │ │ │ │ 示例:lottery_service.go, converter.go │ └─────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────┐ │ Domain Layer (领域层) - 核心! │ │ ───────────────────────────────────────────────── │ │ 职责: │ │ • 领域模型/实体(Entity) │ │ • 值对象(Value Object) │ │ • 领域服务(Domain Service) │ │ • 仓储接口(Repository Interface) │ │ • 领域事件(Domain Event) │ │ │ │ 特点: │ │ • 不依赖基础设施层(通过接口依赖倒置) │ │ • 包含核心业务规则 │ │ • 可以独立测试(纯业务逻辑) │ │ │ │ 示例:lottery.go (领域模型), repository.go (接口) │ └─────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────┐ │ Infrastructure Layer (基础设施层) │ │ ───────────────────────────────────────────────── │ │ 职责: │ │ • 仓储实现(实现 Domain 层定义的接口) │ │ • 数据库访问 │ │ • 缓存访问 │ │ • RPC 客户端 │ │ • 消息队列 │ │ • 外部服务集成 │ │ │ │ 特点: │ │ • 依赖 Domain 层的接口 │ │ • 可替换的实现(如切换数据库) │ │ │ │ 示例:lottery_repo.go, cache_repo.go, user_repo.go │ └─────────────────────────────────────────────────────┘
示例代码 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 package domaintype Lottery struct { ID string UserID int64 PrizeID string Status string DrawTime time.Time func (l *Lottery) CanDraw() bool { return l.Status == "pending" && time.Now().After(l.DrawTime) } func (l *Lottery) Draw() error { if !l.CanDraw() { return errors.New("cannot draw" ) } l.Status = "drawn" return nil } } type LotteryRepository interface { Save(ctx context.Context, lottery *Lottery) error GetByID(ctx context.Context, lotteryID string ) (*Lottery, error ) GetByUserID(ctx context.Context, userID int64 ) ([]*Lottery, error ) } package applicationtype LotteryService struct { lotteryRepo domain.LotteryRepository userRepo UserRepository cacheRepo CacheRepository } func (s *LotteryService) CreateLottery(ctx context.Context, req *CreateLotteryReq) (*LotteryDTO, error ) { user, err := s.userRepo.GetByID(ctx, req.UserID) if err != nil { return nil , fmt.Errorf("get user failed: %w" , err) } lottery := &domain.Lottery{ ID: generateID(), UserID: req.UserID, PrizeID: req.PrizeID, Status: "pending" , DrawTime: time.Now().Add(24 * time.Hour), } if err := s.lotteryRepo.Save(ctx, lottery); err != nil { return nil , fmt.Errorf("save lottery failed: %w" , err) } return convertToDTO(lottery), nil } func convertToDTO (lottery *domain.Lottery) *LotteryDTO { return &LotteryDTO{ ID: lottery.ID, UserID: lottery.UserID, PrizeID: lottery.PrizeID, Status: lottery.Status, DrawTime: lottery.DrawTime.Unix(), } } package infrastructuretype LotteryRepository struct { db *gorm.DB } func (r *LotteryRepository) Save(ctx context.Context, lottery *domain.Lottery) error { po := &LotteryPO{ ID: lottery.ID, UserID: lottery.UserID, PrizeID: lottery.PrizeID, Status: lottery.Status, DrawTime: lottery.DrawTime, } return r.db.WithContext(ctx).Create(po).Error } func (r *LotteryRepository) GetByID(ctx context.Context, lotteryID string ) (*domain.Lottery, error ) { var po LotteryPO if err := r.db.WithContext(ctx).Where("id = ?" , lotteryID).First(&po).Error; err != nil { return nil , err } return &domain.Lottery{ ID: po.ID, UserID: po.UserID, PrizeID: po.PrizeID, Status: po.Status, DrawTime: po.DrawTime, }, nil } type LotteryPO struct { ID string `gorm:"primary_key"` UserID int64 `gorm:"index"` PrizeID string Status string DrawTime time.Time }
DDD 四层架构的优势 :
✅ 领域模型独立 :核心业务逻辑不依赖基础设施
✅ 依赖倒置 :Domain 层定义接口,Infrastructure 层实现
✅ 易于测试 :领域逻辑可以独立测试(不需要数据库)
✅ 易于替换 :可以轻松切换基础设施实现
对比 :
维度
三层架构
DDD 四层架构
复杂度
简单,易于理解
较复杂,需要理解 DDD 概念
领域模型
通常是贫血模型(只有数据)
充血模型(包含业务逻辑)
依赖方向
上层依赖下层
都依赖 Domain 层(依赖倒置)
测试性
Service 需要 mock Repository
Domain 层可以独立测试
适用场景
简单 CRUD 应用
复杂业务逻辑
Pipeline 架构模式(深度实践) 为什么选择 Pipeline? Pipeline 解决的核心问题 在复杂业务中,一个完整的流程往往包含多个步骤:
1 2 3 4 5 6 7 8 9 创建订单流程: 参数校验 → 用户验证 → 库存检查 → 价格计算 → 营销活动 → 优惠券 → 积分 → 风控 → 保存订单 → 支付预创建 → 通知 问题: 1. 这么多步骤写在一个函数里 → 函数过长 2. 步骤之间有依赖关系 → 逻辑复杂 3. 某些步骤可能需要并行执行 → 性能优化困难 4. 某些步骤可能需要跳过 → 条件判断复杂 5. 步骤需要灵活调整顺序 → 代码修改成本高
Pipeline 模式的价值 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ┌────────────────────────────────────────────────────┐ │ Pipeline 模式 = 责任链模式 + 管道模式 │ │ │ │ 核心思想: │ │ 将复杂的处理流程拆分为多个独立的处理器(Processor),│ │ 通过管道(Pipeline)串联起来,数据流经每个处理器, │ │ 最终得到处理结果。 │ │ │ │ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ │ │Proc 1 │→ │Proc 2 │→ │Proc 3 │→ │Proc 4 │ │ │ └────────┘ └────────┘ └────────┘ └────────┘ │ │ │ │ 优势: │ │ ✅ 流程可视化:一目了然看清楚整个处理流程 │ │ ✅ 逻辑解耦:每个 Processor 独立开发和测试 │ │ ✅ 灵活编排:通过配置改变执行顺序 │ │ ✅ 并行优化:支持并行执行多个 Processor │ │ ✅ 易于扩展:新增功能只需添加新 Processor │ └────────────────────────────────────────────────────┘
适用场景 ✅ 适合使用 Pipeline 的场景 :
多步骤的数据处理流程
订单处理:校验 → 计算 → 扣库存 → 保存 → 通知
价格计算:基础价 → 营销价 → 优惠券 → 积分 → 手续费
数据同步:提取 → 转换 → 验证 → 加载(ETL)
需要灵活配置的业务流程
不同品类使用不同的处理流程
不同地区使用不同的处理规则
A/B 测试需要切换不同的处理逻辑
高测试覆盖率要求
团队协作开发
10+ 开发者并行开发不同的 Processor
减少代码冲突
需要监控和调试
❌ 不适合使用 Pipeline 的场景 :
简单的 CRUD 操作
性能要求极高的场景
Pipeline 会引入额外的函数调用开销
延迟敏感(如 P99 < 10ms)
流程固定且变化少
流程几年不变
引入 Pipeline 增加理解成本
Pipeline 架构层次详解 Pipeline 架构分为 4 层:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 ┌──────────────────────────────────────────────────┐ │ Layer 1: Controller (控制层) │ │ • 接收 HTTP 请求 │ │ • 委托给 Service 层 │ └──────────────────────────────────────────────────┘ ↓ ┌──────────────────────────────────────────────────┐ │ Layer 2: Service (服务层) │ │ • 创建 Context │ │ • 执行 Pipeline │ │ • 构建响应 │ └──────────────────────────────────────────────────┘ ↓ ┌──────────────────────────────────────────────────┐ │ Layer 3: Pipeline (管道层) │ │ • 管理 Processor 执行顺序 │ │ • 统一错误处理 │ │ • 支持并行/条件执行 │ └──────────────────────────────────────────────────┘ ↓ ┌──────────────────────────────────────────────────┐ │ Layer 4: Processor (处理器层) │ │ • 实现具体的处理逻辑 │ │ • 读写 Context │ │ • 可独立测试 │ └──────────────────────────────────────────────────┘
Layer 1: Controller Layer (控制层) 职责 :处理 HTTP 请求,委托给 Service 层。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package controllertype FlashSaleController struct { flashSaleService FlashSaleService } func (c *FlashSaleController) FlashSaleListV2(ctx *gin.Context) { var req FlashSaleListReq if err := ctx.ShouldBindJSON(&req); err != nil { ctx.JSON(400 , gin.H{"error" : "invalid request" }) return } resp, err := c.flashSaleService.GetFlashSaleList(ctx, &req) if err != nil { ctx.JSON(500 , gin.H{"error" : err.Error()}) return } ctx.JSON(200 , gin.H{"data" : resp}) }
特点 :
✅ 薄薄的一层,不包含业务逻辑
✅ 负责请求/响应的格式转换
✅ 处理框架相关的逻辑(如参数绑定、响应格式化)
Layer 2: Service Layer (服务层) 职责 :创建 Context,执行 Pipeline,构建响应。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 package servicetype FlashSaleService interface { GetFlashSaleList(ctx context.Context, req *FlashSaleListReq) (*FlashSaleListResp, error ) } type flashSaleService struct { pipeline Pipeline } func NewFlashSaleService () FlashSaleService { pipeline := NewFlashSalePipeline(). AddProcessor(NewValidationProcessor()). AddProcessor(NewPromotionDataProcessor()). AddProcessor(NewItemDataProcessor()). AddProcessor(NewFilterProcessor()). AddProcessor(NewAssemblyProcessor()). AddProcessor(NewSortProcessor()). AddProcessor(NewCacheProcessor()) return &flashSaleService{ pipeline: pipeline, } } func (s *flashSaleService) GetFlashSaleList(ctx context.Context, req *FlashSaleListReq) (*FlashSaleListResp, error ) { fsCtx := &FlashSaleContext{ Request: req, ProcessedAt: time.Now(), } if err := s.pipeline.Execute(ctx, fsCtx); err != nil { return nil , fmt.Errorf("pipeline execute failed: %w" , err) } return s.buildResponse(fsCtx), nil } func (s *flashSaleService) buildResponse(fsCtx *FlashSaleContext) *FlashSaleListResp { return &FlashSaleListResp{ Items: fsCtx.FlashSaleItems, BriefItems: fsCtx.FlashSaleBriefItems, Session: fsCtx.Session, } }
特点 :
✅ 定义业务接口
✅ 管理 Pipeline 的构建和执行
✅ 不包含具体的处理逻辑(委托给 Processor)
Layer 3: Pipeline Layer (管道层) 职责 :管理 Processor 的执行顺序,统一错误处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 package pipelinetype Pipeline interface { AddProcessor(processor Processor) Pipeline Execute(ctx context.Context, fsCtx *FlashSaleContext) error } type flashSalePipeline struct { processors []Processor } func NewFlashSalePipeline () Pipeline { return &flashSalePipeline{ processors: make ([]Processor, 0 ), } } func (p *flashSalePipeline) AddProcessor(processor Processor) Pipeline { p.processors = append (p.processors, processor) return p } func (p *flashSalePipeline) Execute(ctx context.Context, fsCtx *FlashSaleContext) error { for _, processor := range p.processors { if ctx.Err() != nil { return fmt.Errorf("context cancelled: %w" , ctx.Err()) } if err := processor.Process(ctx, fsCtx); err != nil { return fmt.Errorf("processor %s failed: %w" , processor.Name(), err) } } return nil }
特点 :
✅ 管理处理器的执行顺序
✅ 统一的错误处理
✅ 支持流程编排
✅ 可插拔的处理器架构
Layer 4: Processor Layer (处理器层) 职责 :实现具体的处理逻辑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 package processortype Processor interface { Process(ctx context.Context, fsCtx *FlashSaleContext) error Name() string } type PromotionDataProcessor struct { promoService PromotionService } func NewPromotionDataProcessor (promoService PromotionService) Processor { return &PromotionDataProcessor{ promoService: promoService, } } func (p *PromotionDataProcessor) Name() string { return "PromotionDataProcessor" } func (p *PromotionDataProcessor) Process(ctx context.Context, fsCtx *FlashSaleContext) error { promoItems, err := p.promoService.GetActivePromotions(ctx, &PromotionRequest{ Platform: fsCtx.Request.Platform, Region: fsCtx.Request.Region, CategoryID: fsCtx.Request.CategoryID, }) if err != nil { return fmt.Errorf("get promotions failed: %w" , err) } fsCtx.OriginalPromotionItems = promoItems return nil } type FilterProcessor struct {}func NewFilterProcessor () Processor { return &FilterProcessor{} } func (p *FilterProcessor) Name() string { return "FilterProcessor" } func (p *FilterProcessor) Process(ctx context.Context, fsCtx *FlashSaleContext) error { originalItems := fsCtx.OriginalPromotionItems filteredItems := make ([]*PromotionItem, 0 ) for _, item := range originalItems { if item.Stock > 0 && item.Status == "active" && item.StartTime.Before(time.Now()) && item.EndTime.After(time.Now()) { filteredItems = append (filteredItems, item) } } fsCtx.FilteredPromotionItems = filteredItems return nil } type AssemblyProcessor struct {}func NewAssemblyProcessor () Processor { return &AssemblyProcessor{} } func (p *AssemblyProcessor) Name() string { return "AssemblyProcessor" } func (p *AssemblyProcessor) Process(ctx context.Context, fsCtx *FlashSaleContext) error { promoItems := fsCtx.FilteredPromotionItems lsItems := fsCtx.LSItemList flashSaleItems := make ([]*FlashSaleItem, 0 ) for _, promoItem := range promoItems { lsItem := findLSItem(lsItems, promoItem.ItemID) if lsItem == nil { continue } flashSaleItems = append (flashSaleItems, &FlashSaleItem{ ItemID: promoItem.ItemID, ItemName: lsItem.Name, OriginalPrice: lsItem.Price, FlashSalePrice: promoItem.ActivityPrice, Discount: calculateDiscount(lsItem.Price, promoItem.ActivityPrice), Stock: promoItem.Stock, ImageURL: lsItem.ImageURL, }) } fsCtx.FlashSaleItems = flashSaleItems return nil }
特点 :
✅ 实现具体的处理逻辑
✅ 可独立测试
✅ 可重用(同一个 Processor 可以用在不同的 Pipeline)
✅ 职责单一(每个 Processor 只做一件事)
Pipeline 初始化与配置 构建器模式(推荐) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 func NewFlashSaleService () FlashSaleService { promoService := NewPromotionService() itemService := NewItemService() pipeline := NewFlashSalePipeline(). AddProcessor(NewValidationProcessor()). AddProcessor(NewPromotionDataProcessor(promoService)). AddProcessor(NewItemDataProcessor(itemService)). AddProcessor(NewFilterProcessor()). AddProcessor(NewAssemblyProcessor()). AddProcessor(NewSortProcessor()). AddProcessor(NewCacheProcessor()) return &flashSaleService{ pipeline: pipeline, } }
优点 :
✅ 流程一目了然
✅ 支持链式调用
✅ 编译期检查类型
配置驱动(高级,适合大型项目) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 pipelines: flash_sale: processors: - name: validation type: ValidationProcessor enabled: true timeout: 100ms - name: promotion_data type: PromotionDataProcessor enabled: true parallel: true - name: item_data type: ItemDataProcessor enabled: true parallel: true - name: filter type: FilterProcessor enabled: true - name: assembly type: AssemblyProcessor enabled: true - name: sort type: SortProcessor enabled: true config: strategy: discount_first - name: cache type: CacheProcessor enabled: false
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 func NewFlashSaleServiceFromConfig (configPath string ) (FlashSaleService, error ) { config, err := loadPipelineConfig(configPath) if err != nil { return nil , err } factory := NewProcessorFactory() pipeline := NewFlashSalePipeline() for _, procConfig := range config.Processors { if !procConfig.Enabled { continue } processor, err := factory.Create(procConfig.Type, procConfig.Config) if err != nil { return nil , err } pipeline.AddProcessor(processor) } return &flashSaleService{ pipeline: pipeline, }, nil }
优点 :
✅ 可以动态开关某个处理器
✅ 可以调整处理器顺序
✅ 可以配置处理器参数
✅ 支持 A/B 测试(不同配置)
高级特性 并行 Pipeline 某些 Processor 之间没有依赖关系,可以并行执行以提升性能。
“业务步骤互不依赖”还不足以证明并发安全。下面的共享 fsCtx 只允许各 Processor 读取不可变输入并写入互不重叠的字段;不能共同追加 slice、修改同一个 map 或共享日志集合。更通用的做法是每个任务返回独立结果,等待全部退出后由单个 goroutine 合并。所有子任务退出前,不得重置或归还上下文到对象池。[4]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 type ParallelPipeline struct { processors [][]Processor } func (p *ParallelPipeline) Execute(ctx context.Context, fsCtx *FlashSaleContext) error { for _, parallelGroup := range p.processors { if len (parallelGroup) == 1 { if err := parallelGroup[0 ].Process(ctx, fsCtx); err != nil { return err } continue } errChan := make (chan error , len (parallelGroup)) var wg sync.WaitGroup for _, processor := range parallelGroup { wg.Add(1 ) go func (proc Processor) { defer wg.Done() if err := proc.Process(ctx, fsCtx); err != nil { errChan <- fmt.Errorf("processor %s failed: %w" , proc.Name(), err) } }(processor) } wg.Wait() close (errChan) if len (errChan) > 0 { return <-errChan } } return nil } pipeline := NewParallelPipeline() pipeline.AddSequential(NewValidationProcessor()) pipeline.AddParallel([]Processor{ NewPromotionDataProcessor(), NewItemDataProcessor(), }) pipeline.AddSequential(NewAssemblyProcessor())
条件执行 Pipeline 某些 Processor 只在特定条件下执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 type ConditionalProcessor struct { wrapped Processor condition func (*FlashSaleContext) bool } func NewConditionalProcessor (wrapped Processor, condition func (*FlashSaleContext) bool ) Processor { return &ConditionalProcessor{ wrapped: wrapped, condition: condition, } } func (p *ConditionalProcessor) Name() string { return fmt.Sprintf("Conditional(%s)" , p.wrapped.Name()) } func (p *ConditionalProcessor) Process(ctx context.Context, fsCtx *FlashSaleContext) error { if !p.condition(fsCtx) { return nil } return p.wrapped.Process(ctx, fsCtx) } pipeline := NewFlashSalePipeline(). AddProcessor(NewValidationProcessor()). AddProcessor(NewConditionalProcessor( NewNewUserDiscountProcessor(), func (fsCtx *FlashSaleContext) bool { return fsCtx.Request.IsNewUser }, )). AddProcessor(NewAssemblyProcessor())
重试 Pipeline 某些 Processor 可能失败(如网络抖动),支持自动重试。
只有无副作用读取,或已建立幂等契约且失败后不会留下污染中间状态的处理器,才适合包装重试。超时不代表远程操作未发生;参数错误、业务拒绝和取消应直接返回。下面仅重试显式标记为暂时故障的错误,并使用可取消、有上限的指数退避与抖动。业务适配器负责错误分类,系统还需控制总重试预算。[5]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 type RetryProcessor struct { wrapped Processor maxRetries int backoff time.Duration } func NewRetryProcessor (wrapped Processor, maxRetries int , backoff time.Duration) Processor { return &RetryProcessor{ wrapped: wrapped, maxRetries: maxRetries, backoff: backoff, } } func (p *RetryProcessor) Name() string { return fmt.Sprintf("Retry(%s)" , p.wrapped.Name()) } func (p *RetryProcessor) Process(ctx context.Context, fsCtx *FlashSaleContext) error { if p.maxRetries < 0 || p.backoff <= 0 { return errors.New("invalid retry configuration" ) } const maxBackoff = 5 * time.Second delay := p.backoff if delay > maxBackoff { delay = maxBackoff } for attempt := 0 ; ; attempt++ { if err := ctx.Err(); err != nil { return err } err := p.wrapped.Process(ctx, fsCtx) if err == nil { return nil } if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { return err } var transient interface { Retryable() bool } if !errors.As(err, &transient) || !transient.Retryable() { return err } if attempt >= p.maxRetries { return fmt.Errorf("retry exhausted after %d attempts: %w" , attempt+1 , err) } timer := time.NewTimer(time.Duration(rand.Int63n(int64 (delay)))) select { case <-ctx.Done(): timer.Stop() return ctx.Err() case <-timer.C: } if delay >= maxBackoff/2 { delay = maxBackoff } else { delay *= 2 } } } pipeline := NewFlashSalePipeline(). AddProcessor(NewRetryProcessor( NewPromotionDataProcessor(), 3 , 100 *time.Millisecond, ))
超时控制 Pipeline 为每个 Processor 设置超时时间。
Go 的取消信号不会强制终止 goroutine。处理器及其 I/O 必须观察 ctx.Done() 或接收带截止时间的 Context,才会及时退出。[4] 此处同步等待处理器返回,避免超时后仍有后台任务修改 fsCtx;不配合取消的处理器仍可能超出预算,应在依赖层修复。超时也不能撤销已经完成的写入,结果未知时应查询或按幂等协议重试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 type TimeoutProcessor struct { wrapped Processor timeout time.Duration } func NewTimeoutProcessor (wrapped Processor, timeout time.Duration) Processor { return &TimeoutProcessor{ wrapped: wrapped, timeout: timeout, } } func (p *TimeoutProcessor) Name() string { return fmt.Sprintf("Timeout(%s)" , p.wrapped.Name()) } func (p *TimeoutProcessor) Process(ctx context.Context, fsCtx *FlashSaleContext) error { timeoutCtx, cancel := context.WithTimeout(ctx, p.timeout) defer cancel() if err := timeoutCtx.Err(); err != nil { return err } err := p.wrapped.Process(timeoutCtx, fsCtx) if err != nil { return fmt.Errorf("processor %s: %w" , p.wrapped.Name(), err) } if err := timeoutCtx.Err(); err != nil { return fmt.Errorf("processor %s exceeded budget: %w" , p.wrapped.Name(), err) } return nil } pipeline := NewFlashSalePipeline(). AddProcessor(NewTimeoutProcessor( NewPromotionDataProcessor(), 500 *time.Millisecond, ))
Pipeline 最佳实践 Processor 设计原则
无状态 :Processor 应该是无状态的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 type BadProcessor struct { counter int } func (p *BadProcessor) Process(ctx context.Context, fsCtx *FlashSaleContext) error { p.counter++ return nil } type GoodProcessor struct { config Config } func (p *GoodProcessor) Process(ctx context.Context, fsCtx *FlashSaleContext) error { fsCtx.ProcessCount++ return nil }
幂等性 :相同输入应该产生相同输出
1 2 3 4 5 6 7 8 9 10 func (p *FilterProcessor) Process(ctx context.Context, fsCtx *FlashSaleContext) error { filtered := filterItems(fsCtx.OriginalItems, func (item *Item) bool { return item.Stock > 0 }) fsCtx.FilteredItems = filtered return nil }
快速失败 :尽早发现并报告错误
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func (p *ValidationProcessor) Process(ctx context.Context, fsCtx *FlashSaleContext) error { req := fsCtx.Request if req.UserID == 0 { return errors.New("user_id is required" ) } if len (req.Items) == 0 { return errors.New("items is required" ) } return nil }
清晰命名 :Processor 名称要清楚表达职责
1 2 3 4 5 6 7 8 9 type DataProcessor struct {} type Handler struct {} type Helper struct {} type PromotionDataProcessor struct {} type InventoryFilterProcessor struct {} type PriceAssemblyProcessor struct {}
Context 设计原则
分区管理 :Input/Intermediate/Output 明确区分(详见第五章)
类型安全 :避免 interface{}
1 2 3 4 5 6 7 8 9 10 11 type BadContext struct { Data map [string ]interface {} } type GoodContext struct { OriginalItems []*Item FilteredItems []*Item AssembledItems []*AssembledItem }
Pipeline 设计原则
顺序重要 :Processor 的顺序要有逻辑意义
1 2 3 4 5 6 7 8 9 10 11 12 pipeline := NewPipeline(). AddProcessor(NewValidationProcessor()). AddProcessor(NewDataFetchProcessor()). AddProcessor(NewFilterProcessor()). AddProcessor(NewAssemblyProcessor()) pipeline := NewPipeline(). AddProcessor(NewAssemblyProcessor()). AddProcessor(NewFilterProcessor()). AddProcessor(NewDataFetchProcessor())
错误传播 :错误要能正确向上传播
1 2 3 4 5 6 7 8 9 func (p *pipeline) Execute(ctx context.Context, fsCtx *FlashSaleContext) error { for _, processor := range p.processors { if err := processor.Process(ctx, fsCtx); err != nil { return fmt.Errorf("processor %s failed: %w" , processor.Name(), err) } } return nil }
资源管理 :确保资源得到正确释放
1 2 3 4 5 6 7 8 9 10 11 12 13 func (p *ResourceProcessor) Process(ctx context.Context, fsCtx *FlashSaleContext) error { conn, err := getDBConnection() if err != nil { return err } defer conn.Close() return nil }
Context Pattern(上下文模式) 为什么需要 Context? 解决的核心问题
参数传递地狱
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 func step1 (userID int64 , items []Item, region string , platform string ) (*Result1, error ) { return step2(userID, items, region, platform, result1Data) } func step2 (userID int64 , items []Item, region string , platform string , result1 *Result1) (*Result2, error ) { return step3(userID, items, region, platform, result1, result2Data) } func step3 (userID int64 , items []Item, region string , platform string , result1 *Result1, result2 *Result2) (*Result3, error ) { } type ProcessContext struct { UserID int64 Items []Item Region string Platform string Result1 *Result1 Result2 *Result2 Result3 *Result3 } func step1 (ctx *ProcessContext) error { ctx.Result1 = calculateResult1(ctx) return nil } func step2 (ctx *ProcessContext) error { ctx.Result2 = calculateResult2(ctx) return nil } func step3 (ctx *ProcessContext) error { ctx.Result3 = calculateResult3(ctx) return nil }
状态共享
Pipeline 中各 Processor 需要共享数据:
1 Processor 1 产生数据 → Processor 2 读取并处理 → Processor 3 读取并组装
可追溯性
记录完整的处理过程,便于调试和监控:
1 2 3 4 5 6 type Context struct { ProcessedAt time.Time ProcessorLogs []ProcessorLog Errors []error }
Context 设计原则 标准 Context 结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 type FlashSaleContext struct { Request *FlashSaleListReq UserID int64 Region string Platform string IsNewUser bool OriginalPromotionItems []*promotionCmd.ActivityItem FilteredPromotionItems []*promotionCmd.ActivityItem LSItemList []*lsitemcmd.Item UserInfo *UserInfo FlashSaleItems []*FlashSaleItem FlashSaleBriefItems []*FlashSaleBriefItem Session *FlashSaleSession ProcessedAt time.Time ProcessorLogs []ProcessorLog Errors []error } type ProcessorLog struct { ProcessorName string StartTime time.Time EndTime time.Time Duration time.Duration Success bool Error error }
Context 最佳实践
分区管理 :Input/Intermediate/Output 明确区分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 type Context struct { Request *Req TempData1 *Data1 TempData2 *Data2 Response *Resp } type Context struct { Request *Req TempData1 *Data1 Response *Resp TempData2 *Data2 }
类型安全 :避免 interface{}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 type BadContext struct { Data map [string ]interface {} } func (ctx *BadContext) GetData(key string ) interface {} { return ctx.Data[key] } items := ctx.GetData("items" ).([]*Item) type GoodContext struct { Items []*Item Promotions []*Promotion Users []*User } items := ctx.Items
不可变性 :Input 数据只读
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 type Context struct { Request *Req } func (p *Processor) Process(ctx context.Context, fsCtx *FlashSaleContext) error { userID := fsCtx.Request.UserID return nil } type Context struct { Request Req }
清晰命名 :字段名清楚表达含义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 type BadContext struct { Data []interface {} List []string Temp *Temp Flag bool } type GoodContext struct { OriginalPromotionItems []*PromotionItem FilteredItems []*Item AssembledResponse *Response IsNewUser bool }
Context 的生命周期管理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 func (s *flashSaleService) GetFlashSaleList(ctx context.Context, req *Req) (*Resp, error ) { fsCtx := &FlashSaleContext{ Request: req, UserID: req.UserID, Region: req.Region, Platform: req.Platform, ProcessedAt: time.Now(), } if err := s.pipeline.Execute(ctx, fsCtx); err != nil { return nil , err } return s.buildResponse(fsCtx), nil } func (p *PromotionDataProcessor) Process(ctx context.Context, fsCtx *FlashSaleContext) error { req := fsCtx.Request region := fsCtx.Region promoItems, err := p.promoService.GetPromotions(ctx, &PromotionRequest{ Platform: req.Platform, Region: region, CategoryID: req.CategoryID, }) if err != nil { return err } fsCtx.OriginalPromotionItems = promoItems fsCtx.ProcessorLogs = append (fsCtx.ProcessorLogs, ProcessorLog{ ProcessorName: p.Name(), StartTime: time.Now(), EndTime: time.Now(), Success: true , }) return nil } func (p *FilterProcessor) Process(ctx context.Context, fsCtx *FlashSaleContext) error { originalItems := fsCtx.OriginalPromotionItems filteredItems := filterItems(originalItems, func (item *PromotionItem) bool { return item.Stock > 0 && item.Status == "active" }) fsCtx.FilteredPromotionItems = filteredItems return nil } func (p *AssemblyProcessor) Process(ctx context.Context, fsCtx *FlashSaleContext) error { promoItems := fsCtx.FilteredPromotionItems lsItems := fsCtx.LSItemList flashSaleItems := assembleItems(promoItems, lsItems) fsCtx.FlashSaleItems = flashSaleItems return nil }
Context 的高级用法 Context 快照(用于调试) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 type ContextSnapshot struct { StepName string Timestamp time.Time Data interface {} } func (fsCtx *FlashSaleContext) TakeSnapshot(stepName string ) { snapshot := ContextSnapshot{ StepName: stepName, Timestamp: time.Now(), Data: fsCtx.Clone(), } fsCtx.Snapshots = append (fsCtx.Snapshots, snapshot) } func (p *FilterProcessor) Process(ctx context.Context, fsCtx *FlashSaleContext) error { fsCtx.TakeSnapshot("before_filter" ) fsCtx.FilteredItems = filter(fsCtx.OriginalItems) fsCtx.TakeSnapshot("after_filter" ) return nil } for _, snapshot := range fsCtx.Snapshots { fmt.Printf("Step: %s, Time: %v\n" , snapshot.StepName, snapshot.Timestamp) }
Context 验证器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 func (fsCtx *FlashSaleContext) Validate() error { if fsCtx.Request == nil { return errors.New("request is nil" ) } if len (fsCtx.FlashSaleItems) == 0 { return errors.New("no items found" ) } return nil } func (s *flashSaleService) GetFlashSaleList(ctx context.Context, req *Req) (*Resp, error ) { fsCtx := &FlashSaleContext{Request: req} if err := s.pipeline.Execute(ctx, fsCtx); err != nil { return nil , err } if err := fsCtx.Validate(); err != nil { return nil , fmt.Errorf("context validation failed: %w" , err) } return s.buildResponse(fsCtx), nil }
Context 池化(性能优化) 对于高并发场景,可以使用 sync.Pool 复用 Context 对象。
先用 benchmark 与分配分析确认收益,再决定是否池化。对象只有在所有处理器和子任务退出、返回值不再引用其可变数据后才能归还;buildResponse 必须复制需要返回的 slice / map,不能让响应持有池中对象的别名。[6]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 var contextPool = sync.Pool{ New: func () interface {} { return &FlashSaleContext{} }, } func (s *flashSaleService) GetFlashSaleList(ctx context.Context, req *Req) (*Resp, error ) { fsCtx := contextPool.Get().(*FlashSaleContext) defer func () { fsCtx.Reset() contextPool.Put(fsCtx) }() fsCtx.Reset() fsCtx.Request = req fsCtx.UserID = req.UserID fsCtx.ProcessedAt = time.Now() if err := s.pipeline.Execute(ctx, fsCtx); err != nil { return nil , err } return s.buildResponse(fsCtx), nil } func (fsCtx *FlashSaleContext) Reset() { *fsCtx = FlashSaleContext{} }
注意 :池化适合高并发场景,但会增加代码复杂度,需要谨慎使用。
设计模式实战应用 GoF《设计模式》给出了策略、责任链、装饰器、工厂方法、建造者和模板方法的意图与协作结构。[7] 本节把这些模式应用到电商案例中,具体选择仍需由变化原因、职责和成本决定。
策略模式 (Strategy Pattern) 场景:不同的价格计算策略 在电商系统中,不同品类的价格计算逻辑完全不同:
品类
计算逻辑
Topup(充值)
面额 × 折扣率
Hotel(酒店)
间夜数 × 日历价 + 城市税
Flight(机票)
基础票价 + 燃油费 + 机建费 + 选座费
Deal(生活券)
单价 × 数量 - 满减优惠
如果用 if-else 实现,会导致代码难以维护:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 func CalculatePrice (categoryID int64 , order *Order) (*Price, error ) { if categoryID == CategoryTopup { faceValue := order.FaceValue discountRate := getDiscountRate(order.ItemID) finalPrice := int64 (float64 (faceValue) * discountRate) return &Price{Total: finalPrice}, nil } else if categoryID == CategoryHotel { nights := calculateNights(order.CheckIn, order.CheckOut) roomPrice := getRoomPrice(order.RoomID) tax := calculateTax(roomPrice, order.Region) finalPrice := (roomPrice * nights) + tax return &Price{Total: finalPrice}, nil } else if categoryID == CategoryFlight { basePrice := getFlightPrice(order.FlightID) fuelSurcharge := calculateFuelSurcharge(basePrice) airportFee := getAirportFee(order.AirportCode) seatFee := order.SeatPrice finalPrice := basePrice + fuelSurcharge + airportFee + seatFee return &Price{Total: finalPrice}, nil } else if categoryID == CategoryDeal { subtotal := order.UnitPrice * order.Quantity discount := calculateFullReductionDiscount(subtotal) finalPrice := subtotal - discount return &Price{Total: finalPrice}, nil } return nil , errors.New("unsupported category" ) }
问题分析 :
❌ 单个函数包含多个品类的逻辑(违反单一职责)
❌ 新增品类需要修改这个函数(违反开闭原则)
❌ 无法单独测试某个品类的逻辑
❌ 函数过长(380+ 行)
实现:Calculator 接口 + 品类计算器 使用策略模式重构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 type PriceCalculator interface { Calculate(ctx context.Context, order *Order) (*Price, error ) Support(categoryID int64 ) bool Priority() int } type TopupCalculator struct { itemService ItemService } func NewTopupCalculator (itemService ItemService) PriceCalculator { return &TopupCalculator{ itemService: itemService, } } func (c *TopupCalculator) Support(categoryID int64 ) bool { return categoryID >= 10100 && categoryID < 10200 } func (c *TopupCalculator) Priority() int { return 100 } func (c *TopupCalculator) Calculate(ctx context.Context, order *Order) (*Price, error ) { itemInfo, err := c.itemService.GetItem(ctx, order.ItemID) if err != nil { return nil , fmt.Errorf("get item failed: %w" , err) } faceValue := itemInfo.FaceValue discountRate := itemInfo.DiscountRate discountPrice := int64 (float64 (faceValue) * discountRate) totalPrice := discountPrice * int64 (order.Quantity) return &Price{ BasePrice: faceValue * int64 (order.Quantity), DiscountPrice: discountPrice * int64 (order.Quantity), FinalPrice: totalPrice, Breakdown: PriceBreakdown{ Formula: fmt.Sprintf("%d × %.2f × %d = %d" , faceValue, discountRate, order.Quantity, totalPrice), }, }, nil } type HotelCalculator struct { hotelService HotelService } func NewHotelCalculator (hotelService HotelService) PriceCalculator { return &HotelCalculator{ hotelService: hotelService, } } func (c *HotelCalculator) Support(categoryID int64 ) bool { return categoryID >= 10000 && categoryID < 10100 } func (c *HotelCalculator) Priority() int { return 100 } func (c *HotelCalculator) Calculate(ctx context.Context, order *Order) (*Price, error ) { checkIn, _ := time.Parse("2006-01-02" , order.CheckInDate) checkOut, _ := time.Parse("2006-01-02" , order.CheckOutDate) nights := int (checkOut.Sub(checkIn).Hours() / 24 ) roomPrice, err := c.hotelService.GetRoomPrice(ctx, order.RoomID, order.CheckInDate) if err != nil { return nil , fmt.Errorf("get room price failed: %w" , err) } tax := c.calculateTax(roomPrice, nights, order.Region) subtotal := roomPrice * int64 (nights) totalPrice := subtotal + tax return &Price{ BasePrice: subtotal, Tax: tax, FinalPrice: totalPrice, Breakdown: PriceBreakdown{ Formula: fmt.Sprintf("(%d × %d nights) + %d tax = %d" , roomPrice, nights, tax, totalPrice), }, }, nil } func (c *HotelCalculator) calculateTax(roomPrice int64 , nights int , region string ) int64 { taxRates := map [string ]float64 { "SG" : 0.07 , "ID" : 0.10 , "TH" : 0.07 , } rate, ok := taxRates[region] if !ok { rate = 0.05 } return int64 (float64 (roomPrice*int64 (nights)) * rate) } type FlightCalculator struct { flightService FlightService } func NewFlightCalculator (flightService FlightService) PriceCalculator { return &FlightCalculator{ flightService: flightService, } } func (c *FlightCalculator) Support(categoryID int64 ) bool { return categoryID >= 20000 && categoryID < 20100 } func (c *FlightCalculator) Priority() int { return 100 } func (c *FlightCalculator) Calculate(ctx context.Context, order *Order) (*Price, error ) { basePrice, err := c.flightService.GetFlightPrice(ctx, order.FlightID) if err != nil { return nil , fmt.Errorf("get flight price failed: %w" , err) } fuelSurcharge := c.calculateFuelSurcharge(basePrice) airportFee := c.getAirportFee(order.DepartureAirport) seatFee := order.SeatPrice baggageFee := order.BaggagePrice totalPrice := basePrice + fuelSurcharge + airportFee + seatFee + baggageFee return &Price{ BasePrice: basePrice, FuelSurcharge: fuelSurcharge, AirportFee: airportFee, SeatFee: seatFee, BaggageFee: baggageFee, FinalPrice: totalPrice, Breakdown: PriceBreakdown{ Formula: fmt.Sprintf("%d + %d(fuel) + %d(airport) + %d(seat) + %d(baggage) = %d" , basePrice, fuelSurcharge, airportFee, seatFee, baggageFee, totalPrice), }, }, nil } func (c *FlightCalculator) calculateFuelSurcharge(basePrice int64 ) int64 { return int64 (float64 (basePrice) * 0.10 ) } func (c *FlightCalculator) getAirportFee(airportCode string ) int64 { fees := map [string ]int64 { "SIN" : 5000 , "CGK" : 3000 , "BKK" : 4000 , } if fee, ok := fees[airportCode]; ok { return fee } return 2000 } type DefaultCalculator struct { itemService ItemService } func NewDefaultCalculator (itemService ItemService) PriceCalculator { return &DefaultCalculator{ itemService: itemService, } } func (c *DefaultCalculator) Support(categoryID int64 ) bool { return true } func (c *DefaultCalculator) Priority() int { return 0 } func (c *DefaultCalculator) Calculate(ctx context.Context, order *Order) (*Price, error ) { itemInfo, err := c.itemService.GetItem(ctx, order.ItemID) if err != nil { return nil , fmt.Errorf("get item failed: %w" , err) } unitPrice := itemInfo.DiscountPrice if unitPrice == 0 { unitPrice = itemInfo.MarketPrice } totalPrice := unitPrice * int64 (order.Quantity) return &Price{ BasePrice: itemInfo.MarketPrice * int64 (order.Quantity), FinalPrice: totalPrice, }, nil }
策略工厂 + 注册表模式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 type CalculatorFactory struct { calculators []PriceCalculator mu sync.RWMutex } func NewCalculatorFactory () *CalculatorFactory { return &CalculatorFactory{ calculators: make ([]PriceCalculator, 0 ), } } func (f *CalculatorFactory) Register(calculator PriceCalculator) { f.mu.Lock() defer f.mu.Unlock() f.calculators = append (f.calculators, calculator) sort.Slice(f.calculators, func (i, j int ) bool { return f.calculators[i].Priority() > f.calculators[j].Priority() }) } func (f *CalculatorFactory) GetCalculator(categoryID int64 ) PriceCalculator { f.mu.RLock() defer f.mu.RUnlock() for _, calc := range f.calculators { if calc.Support(categoryID) { return calc } } return nil } func InitPricingEngine () *PricingEngine { factory := NewCalculatorFactory() factory.Register(NewTopupCalculator(itemService)) factory.Register(NewHotelCalculator(hotelService)) factory.Register(NewFlightCalculator(flightService)) factory.Register(NewDefaultCalculator(itemService)) return &PricingEngine{ factory: factory, } } func (e *PricingEngine) CalculatePrice(ctx context.Context, order *Order) (*Price, error ) { calculator := e.factory.GetCalculator(order.CategoryID) if calculator == nil { return nil , fmt.Errorf("no calculator found for category %d" , order.CategoryID) } return calculator.Calculate(ctx, order) } func AddNewCategory () { factory.Register(NewMovieCalculator(movieService)) }
策略模式的优势 :
✅ 每个策略独立开发和测试
✅ 新增策略不需要修改现有代码(开闭原则)
✅ 可以动态切换策略
✅ 降低圈复杂度
责任链模式 (Chain of Responsibility) 场景:多级审批流程 订单创建前需要经过多个检查环节:
1 风控检查 → 库存检查 → 价格检查 → 用户额度检查 → 营销规则检查
如果某个环节失败,直接拒绝订单。
实现:Handler 链式调用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 type ApprovalHandler interface { SetNext(handler ApprovalHandler) ApprovalHandler Handle(ctx context.Context, order *Order) error } type baseHandler struct { next ApprovalHandler } func (h *baseHandler) SetNext(handler ApprovalHandler) ApprovalHandler { h.next = handler return handler } type RiskCheckHandler struct { baseHandler riskService RiskService } func NewRiskCheckHandler (riskService RiskService) ApprovalHandler { return &RiskCheckHandler{ riskService: riskService, } } func (h *RiskCheckHandler) Handle(ctx context.Context, order *Order) error { if err := h.riskService.Check(ctx, order); err != nil { return fmt.Errorf("risk check failed: %w" , err) } if h.next != nil { return h.next.Handle(ctx, order) } return nil } type InventoryCheckHandler struct { baseHandler inventoryService InventoryService } func NewInventoryCheckHandler (inventoryService InventoryService) ApprovalHandler { return &InventoryCheckHandler{ inventoryService: inventoryService, } } func (h *InventoryCheckHandler) Handle(ctx context.Context, order *Order) error { for _, item := range order.Items { available, err := h.inventoryService.CheckStock(ctx, item.ItemID, item.Quantity) if err != nil { return fmt.Errorf("check stock failed: %w" , err) } if !available { return fmt.Errorf("item %d out of stock" , item.ItemID) } } if h.next != nil { return h.next.Handle(ctx, order) } return nil } type PriceCheckHandler struct { baseHandler priceService PriceService } func NewPriceCheckHandler (priceService PriceService) ApprovalHandler { return &PriceCheckHandler{ priceService: priceService, } } func (h *PriceCheckHandler) Handle(ctx context.Context, order *Order) error { calculatedPrice, err := h.priceService.Calculate(ctx, order) if err != nil { return fmt.Errorf("calculate price failed: %w" , err) } if abs(calculatedPrice-order.TotalPrice) > calculatedPrice/20 { return fmt.Errorf("price mismatch: expected %d, got %d" , calculatedPrice, order.TotalPrice) } if h.next != nil { return h.next.Handle(ctx, order) } return nil } func CreateOrder (ctx context.Context, order *Order) error { chain := NewRiskCheckHandler(riskService) chain.SetNext(NewInventoryCheckHandler(inventoryService)). SetNext(NewPriceCheckHandler(priceService)). SetNext(NewUserQuotaCheckHandler(quotaService)). SetNext(NewPromotionCheckHandler(promotionService)) if err := chain.Handle(ctx, order); err != nil { return fmt.Errorf("order approval failed: %w" , err) } return saveOrder(ctx, order) }
责任链模式的优势 :
✅ 每个 Handler 职责单一
✅ 可以动态调整 Handler 顺序
✅ 可以动态增加/删除 Handler
✅ 每个 Handler 可以独立测试
责任链 vs Pipeline 对比 :
维度
责任链
Pipeline
核心目的
多个对象处理请求,找到合适的处理者
多个步骤依次处理,完成完整流程
执行方式
找到能处理的就停止
所有步骤都执行
典型场景
审批流程、事件处理
数据转换、流程编排
示例
报销审批(经理→总监→VP)
订单处理(校验→计价→扣库存→保存)
装饰器模式 (Decorator Pattern) 场景:为 Processor 添加通用能力 在 Pipeline 中,我们希望为 Processor 添加日志、监控、缓存、重试等通用能力,但又不想修改每个 Processor 的代码。
实现:装饰器包装 Processor 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 type LoggingProcessor struct { wrapped Processor logger Logger } func NewLoggingProcessor (wrapped Processor, logger Logger) Processor { return &LoggingProcessor{ wrapped: wrapped, logger: logger, } } func (p *LoggingProcessor) Name() string { return p.wrapped.Name() } func (p *LoggingProcessor) Process(ctx context.Context, fsCtx *FlashSaleContext) error { p.logger.Infof("[%s] Start processing" , p.wrapped.Name()) start := time.Now() err := p.wrapped.Process(ctx, fsCtx) duration := time.Since(start) if err != nil { p.logger.Errorf("[%s] Failed after %v: %v" , p.wrapped.Name(), duration, err) } else { p.logger.Infof("[%s] Success in %v" , p.wrapped.Name(), duration) } return err } type MetricsProcessor struct { wrapped Processor metrics Metrics } func NewMetricsProcessor (wrapped Processor, metrics Metrics) Processor { return &MetricsProcessor{ wrapped: wrapped, metrics: metrics, } } func (p *MetricsProcessor) Name() string { return p.wrapped.Name() } func (p *MetricsProcessor) Process(ctx context.Context, fsCtx *FlashSaleContext) error { start := time.Now() err := p.wrapped.Process(ctx, fsCtx) duration := time.Since(start) p.metrics.RecordProcessorLatency(p.wrapped.Name(), duration) if err != nil { p.metrics.RecordProcessorError(p.wrapped.Name(), err.Error()) } else { p.metrics.RecordProcessorSuccess(p.wrapped.Name()) } return err } type CacheProcessor struct { wrapped Processor cache Cache keyFunc func (*FlashSaleContext) string ttl time.Duration } func NewCacheProcessor ( wrapped Processor, cache Cache, keyFunc func (*FlashSaleContext) string , ttl time.Duration, ) Processor { return &CacheProcessor{ wrapped: wrapped, cache: cache, keyFunc: keyFunc, ttl: ttl, } } func (p *CacheProcessor) Name() string { return fmt.Sprintf("Cache(%s)" , p.wrapped.Name()) } func (p *CacheProcessor) Process(ctx context.Context, fsCtx *FlashSaleContext) error { cacheKey := p.keyFunc(fsCtx) if cached := p.cache.Get(cacheKey); cached != nil { fsCtx.FlashSaleItems = cached.([]*FlashSaleItem) return nil } if err := p.wrapped.Process(ctx, fsCtx); err != nil { return err } p.cache.Set(cacheKey, fsCtx.FlashSaleItems, p.ttl) return nil } type CircuitBreakerProcessor struct { wrapped Processor circuitBreaker *CircuitBreaker } func NewCircuitBreakerProcessor (wrapped Processor) Processor { return &CircuitBreakerProcessor{ wrapped: wrapped, circuitBreaker: NewCircuitBreaker(10 , 30 *time.Second), } } func (p *CircuitBreakerProcessor) Name() string { return p.wrapped.Name() } func (p *CircuitBreakerProcessor) Process(ctx context.Context, fsCtx *FlashSaleContext) error { if p.circuitBreaker.IsOpen() { return fmt.Errorf("circuit breaker is open for %s" , p.wrapped.Name()) } err := p.wrapped.Process(ctx, fsCtx) if err != nil { p.circuitBreaker.RecordFailure() } else { p.circuitBreaker.RecordSuccess() } return err } type CircuitBreaker struct { failureCount int failureThreshold int state string lastFailureTime time.Time timeout time.Duration mu sync.Mutex } func NewCircuitBreaker (threshold int , timeout time.Duration) *CircuitBreaker { return &CircuitBreaker{ failureThreshold: threshold, timeout: timeout, state: "closed" , } } func (cb *CircuitBreaker) IsOpen() bool { cb.mu.Lock() defer cb.mu.Unlock() if cb.state == "open" { if time.Since(cb.lastFailureTime) > cb.timeout { cb.state = "half_open" return false } return true } return false } func (cb *CircuitBreaker) RecordFailure() { cb.mu.Lock() defer cb.mu.Unlock() cb.failureCount++ cb.lastFailureTime = time.Now() if cb.failureCount >= cb.failureThreshold { cb.state = "open" } } func (cb *CircuitBreaker) RecordSuccess() { cb.mu.Lock() defer cb.mu.Unlock() cb.failureCount = 0 cb.state = "closed" } func NewFlashSaleService () FlashSaleService { promotionProcessor := NewPromotionDataProcessor(promoService) decoratedProcessor := NewLoggingProcessor( NewMetricsProcessor( NewCacheProcessor( NewCircuitBreakerProcessor( promotionProcessor, ), cache, buildCacheKey, 5 *time.Minute, ), metrics, ), logger, ) pipeline := NewFlashSalePipeline(). AddProcessor(decoratedProcessor) return &flashSaleService{pipeline: pipeline} }
装饰器模式的优势 :
✅ 动态添加功能,不修改原始类
✅ 装饰器可以任意组合
✅ 符合单一职责原则(每个装饰器只负责一个功能)
✅ 符合开闭原则(对扩展开放)
工厂模式 (Factory Pattern) 场景:创建不同类型的 Processor 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 type ProcessorFactory struct { promoService PromotionService itemService ItemService userService UserService cache Cache metrics Metrics logger Logger } func NewProcessorFactory ( promoService PromotionService, itemService ItemService, userService UserService, cache Cache, metrics Metrics, logger Logger, ) *ProcessorFactory { return &ProcessorFactory{ promoService: promoService, itemService: itemService, userService: userService, cache: cache, metrics: metrics, logger: logger, } } func (f *ProcessorFactory) CreateProcessor(processorType string ) (Processor, error ) { switch processorType { case "validation" : return NewValidationProcessor(), nil case "promotion_data" : processor := NewPromotionDataProcessor(f.promoService) return f.decorateProcessor(processor), nil case "item_data" : processor := NewItemDataProcessor(f.itemService) return f.decorateProcessor(processor), nil case "filter" : return NewFilterProcessor(), nil case "assembly" : return NewAssemblyProcessor(), nil case "sort" : return NewSortProcessor(), nil default : return nil , fmt.Errorf("unknown processor type: %s" , processorType) } } func (f *ProcessorFactory) decorateProcessor(processor Processor) Processor { return NewLoggingProcessor( NewMetricsProcessor( NewCircuitBreakerProcessor(processor), f.metrics, ), f.logger, ) } func NewFlashSaleServiceWithFactory (factory *ProcessorFactory) (FlashSaleService, error ) { pipeline := NewFlashSalePipeline() processorTypes := []string { "validation" , "promotion_data" , "item_data" , "filter" , "assembly" , "sort" , } for _, typ := range processorTypes { processor, err := factory.CreateProcessor(typ) if err != nil { return nil , err } pipeline.AddProcessor(processor) } return &flashSaleService{pipeline: pipeline}, nil }
建造者模式 (Builder Pattern) 场景:构建复杂的 Pipeline 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 type PipelineBuilder struct { processors []Processor logger Logger metrics Metrics cache Cache enableLogging bool enableMetrics bool enableCache bool enableCircuitBreaker bool } func NewPipelineBuilder () *PipelineBuilder { return &PipelineBuilder{ processors: make ([]Processor, 0 ), enableLogging: true , enableMetrics: true , enableCache: false , enableCircuitBreaker: false , } } func (b *PipelineBuilder) WithLogger(logger Logger) *PipelineBuilder { b.logger = logger return b } func (b *PipelineBuilder) WithMetrics(metrics Metrics) *PipelineBuilder { b.metrics = metrics return b } func (b *PipelineBuilder) WithCache(cache Cache) *PipelineBuilder { b.cache = cache return b } func (b *PipelineBuilder) EnableLogging(enable bool ) *PipelineBuilder { b.enableLogging = enable return b } func (b *PipelineBuilder) EnableMetrics(enable bool ) *PipelineBuilder { b.enableMetrics = enable return b } func (b *PipelineBuilder) EnableCache(enable bool ) *PipelineBuilder { b.enableCache = enable return b } func (b *PipelineBuilder) EnableCircuitBreaker(enable bool ) *PipelineBuilder { b.enableCircuitBreaker = enable return b } func (b *PipelineBuilder) AddProcessor(processor Processor) *PipelineBuilder { decorated := processor if b.enableCircuitBreaker { decorated = NewCircuitBreakerProcessor(decorated) } if b.enableCache && b.cache != nil { decorated = NewCacheProcessor(decorated, b.cache, nil , 5 *time.Minute) } if b.enableMetrics && b.metrics != nil { decorated = NewMetricsProcessor(decorated, b.metrics) } if b.enableLogging && b.logger != nil { decorated = NewLoggingProcessor(decorated, b.logger) } b.processors = append (b.processors, decorated) return b } func (b *PipelineBuilder) Build() Pipeline { pipeline := NewFlashSalePipeline() for _, processor := range b.processors { pipeline.AddProcessor(processor) } return pipeline } func NewFlashSaleService () FlashSaleService { pipeline := NewPipelineBuilder(). WithLogger(logger). WithMetrics(metrics). WithCache(cache). EnableLogging(true ). EnableMetrics(true ). EnableCache(false ). EnableCircuitBreaker(true ). AddProcessor(NewValidationProcessor()). AddProcessor(NewPromotionDataProcessor(promoService)). AddProcessor(NewItemDataProcessor(itemService)). AddProcessor(NewFilterProcessor()). AddProcessor(NewAssemblyProcessor()). AddProcessor(NewSortProcessor()). Build() return &flashSaleService{pipeline: pipeline} }
建造者模式的优势 :
✅ 支持链式调用,代码优雅
✅ 参数可选,灵活配置
✅ 隐藏复杂的构建逻辑
✅ 支持不同的构建配置
模板方法模式 (Template Method) 场景:标准化 Processor 的执行流程 所有 Processor 都有相同的执行流程:前置处理 → 主要逻辑 → 后置处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 type BaseProcessor struct { processorName string } func (p *BaseProcessor) Process(ctx context.Context, fsCtx *FlashSaleContext) error { if err := p.preProcess(ctx, fsCtx); err != nil { return fmt.Errorf("pre-process failed: %w" , err) } if err := p.doProcess(ctx, fsCtx); err != nil { return fmt.Errorf("do-process failed: %w" , err) } if err := p.postProcess(ctx, fsCtx); err != nil { return fmt.Errorf("post-process failed: %w" , err) } return nil } func (p *BaseProcessor) preProcess(ctx context.Context, fsCtx *FlashSaleContext) error { log.Infof("Start processing: %s" , p.processorName) return nil } func (p *BaseProcessor) doProcess(ctx context.Context, fsCtx *FlashSaleContext) error { return nil } func (p *BaseProcessor) postProcess(ctx context.Context, fsCtx *FlashSaleContext) error { log.Infof("End processing: %s" , p.processorName) return nil } type PromotionDataProcessor struct { BaseProcessor promoService PromotionService } func NewPromotionDataProcessor (promoService PromotionService) Processor { return &PromotionDataProcessor{ BaseProcessor: BaseProcessor{processorName: "PromotionDataProcessor" }, promoService: promoService, } } func (p *PromotionDataProcessor) Name() string { return p.processorName } func (p *PromotionDataProcessor) doProcess(ctx context.Context, fsCtx *FlashSaleContext) error { promoItems, err := p.promoService.GetActivePromotions(ctx, fsCtx.Request) if err != nil { return err } fsCtx.OriginalPromotionItems = promoItems return nil } func (p *PromotionDataProcessor) preProcess(ctx context.Context, fsCtx *FlashSaleContext) error { if err := p.BaseProcessor.preProcess(ctx, fsCtx); err != nil { return err } if fsCtx.Request == nil { return errors.New("request is nil" ) } return nil }
模板方法模式的优势 :
✅ 标准化流程,避免遗漏步骤
✅ 复用通用逻辑
✅ 子类只需实现核心逻辑
✅ 易于维护和扩展
规则引擎模式 为什么需要规则引擎? 在电商/金融等业务中,存在大量频繁变化的业务规则 :
业务场景
规则示例
营销活动
“新用户首单立减20元” “满100减15” “连续签到7天送券”
风控规则
“单日交易次数 > 10 次触发风控” “交易金额 > 1000 且用户注册时间 < 3天,需要人工审核”
定价规则
“VIP 用户享受 95 折” “周末酒店价格上浮 20%”
审批流程
“金额 < 1000 经理审批” “金额 >= 1000 且 < 10000 总监审批”
传统硬编码的问题 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 func ApplyDiscount (user *User, order *Order) int64 { discount := int64 (0 ) if user.IsNewUser && user.OrderCount == 0 { discount += 2000 } if order.Subtotal >= 10000 { discount += 1500 } if user.VIPLevel >= 3 { discount += int64 (float64 (order.Subtotal) * 0.05 ) } if order.CategoryID == CategoryHotel && isWeekend() { discount -= int64 (float64 (order.Subtotal) * 0.20 ) } return discount }
硬编码的痛点 :
❌ 每次规则变更都需要修改代码、发布上线(耗时 1-2 天)
❌ 规则逻辑分散在多个函数中,难以维护
❌ 运营无法自主配置规则,完全依赖研发
❌ 规则之间的优先级、互斥关系难以管理
❌ 无法灰度验证规则效果
规则引擎架构 规则引擎的核心思想:将业务规则从代码中分离出来,存储为数据(配置),由引擎动态解析执行 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 ┌─────────────────────────────────────────────────────┐ │ 业务代码层 │ │ (调用规则引擎,传入上下文,获取执行结果) │ └────────────────┬────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────┐ │ 规则引擎 │ │ ┌─────────────────────────────────────────────┐ │ │ │ 规则加载器 (Rule Loader) │ │ │ │ - 从数据库/配置文件加载规则 │ │ │ │ - 缓存规则、支持热更新 │ │ │ └─────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────┐ │ │ │ 规则引擎核心 (Rule Engine) │ │ │ │ - 解析规则表达式 │ │ │ │ - 执行规则匹配 │ │ │ │ - 执行规则动作 │ │ │ └─────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────┐ │ │ │ 规则执行器 (Rule Executor) │ │ │ │ - 优先级排序 │ │ │ │ - 互斥规则检查 │ │ │ │ - 执行动作 │ │ │ └─────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────┐ │ 规则存储层 │ │ - MySQL/PostgreSQL (规则配置) │ │ - Redis (规则缓存) │ │ - YAML/JSON (静态规则) │ └─────────────────────────────────────────────────────┘
规则引擎的核心组件 :
规则定义 :描述规则的条件和动作
规则加载器 :从存储层加载规则,支持缓存和热更新
规则引擎 :解析和执行规则
规则执行器 :管理规则的优先级、互斥关系
规则引擎实战案例 方案一:配置驱动的轻量级规则引擎 适用于规则数量少(< 100 个)、逻辑简单 的场景。
Step 1: 定义规则结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 type Rule struct { ID int64 `json:"id"` Name string `json:"name"` Description string `json:"description"` Priority int `json:"priority"` Enabled bool `json:"enabled"` StartTime time.Time `json:"start_time"` EndTime time.Time `json:"end_time"` Conditions []Condition `json:"conditions"` Actions []Action `json:"actions"` MutexRules []int64 `json:"mutex_rules"` } type Condition struct { Field string `json:"field"` Operator string `json:"operator"` Value interface {} `json:"value"` LogicOp string `json:"logic_op"` } type Action struct { Type string `json:"type"` Params map [string ]interface {} `json:"params"` }
Step 2: 规则配置示例(YAML) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 rules: - id: 1001 name: "新用户首单立减20元" description: "新注册用户的第一笔订单立减20元" priority: 100 enabled: true start_time: "2024-01-01T00:00:00Z" end_time: "2024-12-31T23:59:59Z" conditions: - field: "user.is_new_user" operator: "==" value: true logic_op: "AND" - field: "user.order_count" operator: "==" value: 0 actions: - type: "discount" params: amount: 2000 reason: "新用户首单优惠" mutex_rules: [] - id: 1002 name: "满100减15" description: "订单金额满100元减15元" priority: 80 enabled: true start_time: "2024-01-01T00:00:00Z" end_time: "2024-12-31T23:59:59Z" conditions: - field: "order.subtotal" operator: ">=" value: 10000 actions: - type: "discount" params: amount: 1500 reason: "满100减15" mutex_rules: [1001 ] - id: 1003 name: "VIP用户95折" description: "VIP等级>=3的用户享受95折" priority: 90 enabled: true start_time: "2024-01-01T00:00:00Z" end_time: "2024-12-31T23:59:59Z" conditions: - field: "user.vip_level" operator: ">=" value: 3 actions: - type: "discount_rate" params: rate: 0.05 reason: "VIP用户折扣" mutex_rules: [] - id: 1004 name: "周末酒店上浮20%" description: "周末酒店订单价格上浮20%" priority: 110 enabled: true start_time: "2024-01-01T00:00:00Z" end_time: "2024-12-31T23:59:59Z" conditions: - field: "order.category_id" operator: "==" value: 10000 logic_op: "AND" - field: "order.is_weekend" operator: "==" value: true actions: - type: "charge_rate" params: rate: 0.20 reason: "周末酒店价格调整" mutex_rules: []
Step 3: 规则引擎实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 type RuleEngine struct { rules []*Rule cache Cache mu sync.RWMutex } func NewRuleEngine () *RuleEngine { return &RuleEngine{ rules: make ([]*Rule, 0 ), } } func (e *RuleEngine) LoadRules(filePath string ) error { data, err := ioutil.ReadFile(filePath) if err != nil { return fmt.Errorf("read rules file failed: %w" , err) } var config struct { Rules []*Rule `yaml:"rules"` } if err := yaml.Unmarshal(data, &config); err != nil { return fmt.Errorf("unmarshal rules failed: %w" , err) } e.mu.Lock() defer e.mu.Unlock() sort.Slice(config.Rules, func (i, j int ) bool { return config.Rules[i].Priority > config.Rules[j].Priority }) e.rules = config.Rules log.Infof("Loaded %d rules" , len (e.rules)) return nil } func (e *RuleEngine) Execute(ctx context.Context, input *RuleInput) (*RuleOutput, error ) { e.mu.RLock() defer e.mu.RUnlock() output := &RuleOutput{ MatchedRules: make ([]*Rule, 0 ), AppliedDiscounts: make ([]DiscountItem, 0 ), } matchedRuleIDs := make (map [int64 ]bool ) for _, rule := range e.rules { if !rule.Enabled { continue } now := time.Now() if now.Before(rule.StartTime) || now.After(rule.EndTime) { continue } if e.hasConflict(rule, matchedRuleIDs) { log.Infof("Rule %d conflicts with matched rules, skip" , rule.ID) continue } matched, err := e.evaluateConditions(rule.Conditions, input) if err != nil { log.Errorf("Evaluate rule %d failed: %v" , rule.ID, err) continue } if !matched { continue } if err := e.executeActions(rule.Actions, input, output); err != nil { log.Errorf("Execute rule %d actions failed: %v" , rule.ID, err) continue } output.MatchedRules = append (output.MatchedRules, rule) matchedRuleIDs[rule.ID] = true log.Infof("Rule %d (%s) matched and applied" , rule.ID, rule.Name) } return output, nil } func (e *RuleEngine) hasConflict(rule *Rule, matchedRuleIDs map [int64 ]bool ) bool { for _, mutexRuleID := range rule.MutexRules { if matchedRuleIDs[mutexRuleID] { return true } } return false } func (e *RuleEngine) evaluateConditions(conditions []Condition, input *RuleInput) (bool , error ) { if len (conditions) == 0 { return true , nil } result := true for i, condition := range conditions { fieldValue, err := e.getFieldValue(condition.Field, input) if err != nil { return false , err } matched, err := e.compare(fieldValue, condition.Operator, condition.Value) if err != nil { return false , err } if i == 0 { result = matched } else { prevCondition := conditions[i-1 ] if prevCondition.LogicOp == "AND" { result = result && matched } else if prevCondition.LogicOp == "OR" { result = result || matched } } } return result, nil } func (e *RuleEngine) getFieldValue(field string , input *RuleInput) (interface {}, error ) { parts := strings.Split(field, "." ) switch parts[0 ] { case "user" : return e.getUserField(parts[1 :], input.User) case "order" : return e.getOrderField(parts[1 :], input.Order) default : return nil , fmt.Errorf("unknown field prefix: %s" , parts[0 ]) } } func (e *RuleEngine) getUserField(path []string , user *User) (interface {}, error ) { if len (path) == 0 { return nil , errors.New("empty field path" ) } switch path[0 ] { case "is_new_user" : return user.IsNewUser, nil case "order_count" : return user.OrderCount, nil case "vip_level" : return user.VIPLevel, nil default : return nil , fmt.Errorf("unknown user field: %s" , path[0 ]) } } func (e *RuleEngine) getOrderField(path []string , order *Order) (interface {}, error ) { if len (path) == 0 { return nil , errors.New("empty field path" ) } switch path[0 ] { case "subtotal" : return order.Subtotal, nil case "category_id" : return order.CategoryID, nil case "is_weekend" : return order.IsWeekend, nil default : return nil , fmt.Errorf("unknown order field: %s" , path[0 ]) } } func (e *RuleEngine) compare(fieldValue interface {}, operator string , targetValue interface {}) (bool , error ) { switch operator { case "==" : return fieldValue == targetValue, nil case "!=" : return fieldValue != targetValue, nil case ">" : fv, ok1 := fieldValue.(int64 ) tv, ok2 := targetValue.(int64 ) if !ok1 || !ok2 { fvi, ok1 := fieldValue.(int ) tvi, ok2 := targetValue.(int ) if ok1 && ok2 { return fvi > tvi, nil } return false , errors.New("type mismatch for > operator" ) } return fv > tv, nil case ">=" : fv, ok1 := fieldValue.(int64 ) tv, ok2 := targetValue.(int64 ) if !ok1 || !ok2 { fvi, ok1 := fieldValue.(int ) tvi, ok2 := targetValue.(int ) if ok1 && ok2 { return fvi >= tvi, nil } return false , errors.New("type mismatch for >= operator" ) } return fv >= tv, nil case "<" : fv, ok1 := fieldValue.(int64 ) tv, ok2 := targetValue.(int64 ) if !ok1 || !ok2 { return false , errors.New("type mismatch for < operator" ) } return fv < tv, nil case "<=" : fv, ok1 := fieldValue.(int64 ) tv, ok2 := targetValue.(int64 ) if !ok1 || !ok2 { return false , errors.New("type mismatch for <= operator" ) } return fv <= tv, nil case "in" : targetList, ok := targetValue.([]interface {}) if !ok { return false , errors.New("target value must be a list for 'in' operator" ) } for _, item := range targetList { if fieldValue == item { return true , nil } } return false , nil case "not_in" : targetList, ok := targetValue.([]interface {}) if !ok { return false , errors.New("target value must be a list for 'not_in' operator" ) } for _, item := range targetList { if fieldValue == item { return false , nil } } return true , nil default : return false , fmt.Errorf("unknown operator: %s" , operator) } } func (e *RuleEngine) executeActions(actions []Action, input *RuleInput, output *RuleOutput) error { for _, action := range actions { switch action.Type { case "discount" : amount, ok := action.Params["amount" ].(int64 ) if !ok { amountFloat, ok := action.Params["amount" ].(float64 ) if !ok { return errors.New("invalid discount amount" ) } amount = int64 (amountFloat) } reason, _ := action.Params["reason" ].(string ) output.AppliedDiscounts = append (output.AppliedDiscounts, DiscountItem{ Type: "fixed" , Amount: amount, Reason: reason, }) case "discount_rate" : rate, ok := action.Params["rate" ].(float64 ) if !ok { return errors.New("invalid discount rate" ) } reason, _ := action.Params["reason" ].(string ) discountAmount := int64 (float64 (input.Order.Subtotal) * rate) output.AppliedDiscounts = append (output.AppliedDiscounts, DiscountItem{ Type: "rate" , Amount: discountAmount, Reason: reason, }) case "charge_rate" : rate, ok := action.Params["rate" ].(float64 ) if !ok { return errors.New("invalid charge rate" ) } reason, _ := action.Params["reason" ].(string ) chargeAmount := int64 (float64 (input.Order.Subtotal) * rate) output.AppliedDiscounts = append (output.AppliedDiscounts, DiscountItem{ Type: "charge" , Amount: -chargeAmount, Reason: reason, }) default : return fmt.Errorf("unknown action type: %s" , action.Type) } } return nil } type RuleInput struct { User *User Order *Order } type RuleOutput struct { MatchedRules []*Rule AppliedDiscounts []DiscountItem } type DiscountItem struct { Type string Amount int64 Reason string } type User struct { UserID int64 IsNewUser bool OrderCount int VIPLevel int } type Order struct { OrderID int64 Subtotal int64 CategoryID int64 IsWeekend bool }
Step 4: 使用规则引擎 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 func main () { engine := NewRuleEngine() if err := engine.LoadRules("rules.yaml" ); err != nil { log.Fatalf("Load rules failed: %v" , err) } input := &RuleInput{ User: &User{ UserID: 123 , IsNewUser: true , OrderCount: 0 , VIPLevel: 1 , }, Order: &Order{ OrderID: 456 , Subtotal: 12000 , CategoryID: 10000 , IsWeekend: false , }, } output, err := engine.Execute(context.Background(), input) if err != nil { log.Fatalf("Execute rules failed: %v" , err) } fmt.Printf("Matched %d rules:\n" , len (output.MatchedRules)) for _, rule := range output.MatchedRules { fmt.Printf("- %s (priority=%d)\n" , rule.Name, rule.Priority) } fmt.Printf("\nApplied discounts:\n" ) totalDiscount := int64 (0 ) for _, discount := range output.AppliedDiscounts { fmt.Printf("- %s: %d (type=%s)\n" , discount.Reason, discount.Amount, discount.Type) totalDiscount += discount.Amount } fmt.Printf("\nTotal discount: %d\n" , totalDiscount) fmt.Printf("Final price: %d\n" , input.Order.Subtotal-totalDiscount) }
输出 :
1 2 3 4 5 6 7 8 9 Matched 2 rules: - 新用户首单立减20元 (priority=100) - 满100减15 (priority=80) ← 因为互斥,实际不会应用 Applied discounts: - 新用户首单优惠: 2000 (type=fixed) Total discount: 2000 Final price: 10000 # 120.00 - 20.00 = 100.00 元
方案二:集成开源规则引擎(gengine) 适用于规则数量多(> 100 个)、逻辑复杂 的场景。
安装 gengine 1 go get github.com/bilibili/gengine@latest
使用 gengine 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 package mainimport ( "fmt" "github.com/bilibili/gengine/builder" "github.com/bilibili/gengine/context" "github.com/bilibili/gengine/engine" ) type DiscountCalculator struct { TotalDiscount int64 Reasons []string } func (dc *DiscountCalculator) AddDiscount(amount int64 , reason string ) { dc.TotalDiscount += amount dc.Reasons = append (dc.Reasons, reason) } const ruleContent = ` rule "新用户首单立减20元" "新用户首单优惠规则" salience 100 begin if user.IsNewUser && user.OrderCount == 0 { calculator.AddDiscount(2000, "新用户首单立减20元") } end rule "满100减15" "满减优惠" salience 80 begin if order.Subtotal >= 10000 { calculator.AddDiscount(1500, "满100减15") } end rule "VIP用户95折" "VIP折扣" salience 90 begin if user.VIPLevel >= 3 { discount := order.Subtotal * 5 / 100 calculator.AddDiscount(discount, "VIP用户95折") } end rule "周末酒店上浮20%" "周末价格调整" salience 110 begin if order.CategoryID == 10000 && order.IsWeekend { charge := order.Subtotal * 20 / 100 calculator.AddDiscount(-charge, "周末酒店上浮20%") } end ` func main () { dataContext := context.NewDataContext() user := &User{ UserID: 123 , IsNewUser: true , OrderCount: 0 , VIPLevel: 1 , } order := &Order{ OrderID: 456 , Subtotal: 12000 , CategoryID: 10000 , IsWeekend: false , } calculator := &DiscountCalculator{ TotalDiscount: 0 , Reasons: make ([]string , 0 ), } dataContext.Add("user" , user) dataContext.Add("order" , order) dataContext.Add("calculator" , calculator) knowledgeContext := builder.NewKnowledgeBuilder() if err := knowledgeContext.BuildRuleFromString(ruleContent); err != nil { panic (err) } eng := engine.NewGengine() if err := eng.Execute(knowledgeContext, dataContext, true ); err != nil { panic (err) } fmt.Printf("Total discount: %d\n" , calculator.TotalDiscount) fmt.Printf("Reasons:\n" ) for _, reason := range calculator.Reasons { fmt.Printf("- %s\n" , reason) } fmt.Printf("Final price: %d\n" , order.Subtotal-calculator.TotalDiscount) }
规则引擎的优势
维度
硬编码
规则引擎
规则变更
修改代码 + 发布(1-2 天)
修改配置,热更新(5 分钟)
运营自主性
完全依赖研发
运营可自主配置规则
规则可见性
分散在代码中,难以查看
集中管理,清晰可见
规则测试
需要单元测试
可以通过配置灰度验证
优先级管理
代码中硬编码 if-else 顺序
通过 priority 字段控制
互斥关系
代码中手动判断
通过 mutex_rules 自动处理
规则引擎最佳实践 什么时候使用规则引擎? 适合使用规则引擎的场景 :
✅ 规则频繁变化(每周都有新活动)
✅ 规则逻辑复杂(条件多、组合多)
✅ 运营需要自主配置
✅ 需要灰度验证规则效果
不适合使用规则引擎的场景 :
❌ 规则稳定,很少变化
❌ 规则逻辑简单(1-2 个 if 判断)
❌ 规则需要调用复杂的外部服务
规则引擎 vs 代码的边界 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ┌───────────────────────────────────────────────┐ │ 应用代码层 │ │ - 数据加载(从 DB、RPC 获取数据) │ │ - 数据预处理(转换、聚合) │ │ - 调用规则引擎 │ │ - 结果后处理(落库、发送消息) │ └───────────────────────────────────────────────┘ │ ▼ ┌───────────────────────────────────────────────┐ │ 规则引擎层 │ │ - 规则匹配(根据条件筛选规则) │ │ - 规则执行(应用折扣、设置字段) │ │ - 优先级排序、互斥处理 │ └───────────────────────────────────────────────┘
原则 :
规则引擎只负责纯计算逻辑
数据加载、RPC 调用 应该在应用层完成
规则引擎的监控与告警 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 type RuleMetrics struct { RuleID int64 RuleName string ExecutionCount int64 MatchCount int64 MatchRate float64 AvgDuration time.Duration ErrorCount int64 } func (e *RuleEngine) recordMetrics(rule *Rule, duration time.Duration, matched bool , err error ) { metrics := e.getOrCreateMetrics(rule.ID) metrics.ExecutionCount++ if matched { metrics.MatchCount++ } metrics.MatchRate = float64 (metrics.MatchCount) / float64 (metrics.ExecutionCount) metrics.AvgDuration = (metrics.AvgDuration*time.Duration(metrics.ExecutionCount-1 ) + duration) / time.Duration(metrics.ExecutionCount) if err != nil { metrics.ErrorCount++ } prometheusRuleExecutionCount.WithLabelValues(rule.Name).Inc() prometheusRuleMatchRate.WithLabelValues(rule.Name).Set(metrics.MatchRate) prometheusRuleDuration.WithLabelValues(rule.Name).Observe(duration.Seconds()) }
开源规则引擎推荐
规则引擎
语言
特点
适用场景
gengine [8]
Go
B 站开源,支持规则 DSL
规则表达与执行
Drools
Java
老牌规则引擎,功能强大
Java 生态
Easy Rules
Java
轻量级,易于上手
简单规则场景
自研配置驱动
-
灵活可控
简单到中等复杂度
推荐方案 :
小型项目(< 50 个规则) :自研配置驱动的规则引擎
中大型项目(> 50 个规则) :使用 gengine 等开源方案
规则极其复杂 :考虑 Drools(需要 Java)
代码组织最佳实践 好的代码组织能让项目结构清晰、职责分明,降低理解成本。本章将介绍 Go 项目的标准组织方式。
项目结构模板(参考 nsf-lotto) 标准 Go 项目结构(DDD 四层架构) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 nsf-lotto/ ├── cmd/ # 应用程序入口 │ └── server/ │ └── main.go # 主函数 │ ├── internal/ # 私有代码(不对外暴露) │ ├── application/ # 应用层(Application Layer) │ │ ├── service/ # 应用服务 │ │ │ ├── lotto_service.go # 抽奖服务 │ │ │ ├── prize_service.go # 奖品服务 │ │ │ └── activity_service.go # 活动服务 │ │ │ │ │ ├── dto/ # 数据传输对象 │ │ │ ├── lotto_dto.go # 抽奖 DTO │ │ │ └── prize_dto.go # 奖品 DTO │ │ │ │ │ └── assembler/ # 组装器(DTO ↔ Domain Entity) │ │ ├── lotto_assembler.go │ │ └── prize_assembler.go │ │ │ ├── domain/ # 领域层(Domain Layer) │ │ ├── entity/ # 实体 │ │ │ ├── lotto.go # 抽奖实体 │ │ │ ├── prize.go # 奖品实体 │ │ │ └── activity.go # 活动实体 │ │ │ │ │ ├── valueobject/ # 值对象 │ │ │ ├── prize_type.go # 奖品类型 │ │ │ ├── lotto_status.go # 抽奖状态 │ │ │ └── probability.go # 概率值对象 │ │ │ │ │ ├── repository/ # 仓储接口(Interface) │ │ │ ├── lotto_repository.go │ │ │ └── prize_repository.go │ │ │ │ │ └── service/ # 领域服务 │ │ ├── lotto_domain_service.go # 抽奖领域服务 │ │ └── prize_allocation_service.go │ │ │ ├── infrastructure/ # 基础设施层(Infrastructure Layer) │ │ ├── persistence/ # 持久化 │ │ │ ├── mysql/ │ │ │ │ ├── lotto_repository_impl.go # 仓储实现 │ │ │ │ └── prize_repository_impl.go │ │ │ │ │ │ │ ├── redis/ │ │ │ │ └── lotto_cache.go # 缓存实现 │ │ │ │ │ │ │ └── model/ # 数据库模型(PO) │ │ │ ├── lotto_po.go │ │ │ └── prize_po.go │ │ │ │ │ ├── rpc/ # RPC 客户端 │ │ │ ├── user_client.go # 用户服务客户端 │ │ │ └── item_client.go # 商品服务客户端 │ │ │ │ │ ├── mq/ # 消息队列 │ │ │ ├── producer/ │ │ │ │ └── lotto_event_producer.go │ │ │ └── consumer/ │ │ │ └── lotto_event_consumer.go │ │ │ │ │ └── config/ # 配置 │ │ ├── config.go │ │ └── config.yaml │ │ │ ├── interfaces/ # 接口层(Interface Layer / Adapter Layer) │ │ ├── http/ # HTTP 控制器 │ │ │ ├── handler/ │ │ │ │ ├── lotto_handler.go # 抽奖接口 │ │ │ │ └── prize_handler.go # 奖品接口 │ │ │ │ │ │ │ ├── middleware/ # 中间件 │ │ │ │ ├── auth.go │ │ │ │ └── rate_limit.go │ │ │ │ │ │ │ └── router.go # 路由 │ │ │ │ │ └── grpc/ # gRPC 服务 │ │ ├── lotto_grpc_service.go │ │ └── pb/ # Protobuf 生成文件 │ │ └── lotto.pb.go │ │ │ ├── processor/ # Pipeline Processor(跨层) │ │ ├── processor.go # Processor 接口 │ │ ├── validation_processor.go # 校验处理器 │ │ ├── data_prepare_processor.go # 数据准备处理器 │ │ ├── lottery_processor.go # 抽奖处理器 │ │ └── result_assembly_processor.go │ │ │ └── pipeline/ # Pipeline 编排(跨层) │ ├── pipeline.go # Pipeline 接口 │ └── lotto_pipeline.go # 抽奖流程编排 │ ├── pkg/ # 公共库(可对外复用) │ ├── errors/ # 错误定义 │ │ ├── error_code.go │ │ └── business_error.go │ │ │ ├── utils/ # 工具类 │ │ ├── json_util.go │ │ └── time_util.go │ │ │ └── logger/ # 日志 │ └── logger.go │ ├── configs/ # 配置文件 │ ├── config.yaml # 默认配置 │ ├── config.dev.yaml # 开发环境 │ └── config.prod.yaml # 生产环境 │ ├── scripts/ # 脚本 │ ├── build.sh # 构建脚本 │ ├── deploy.sh # 部署脚本 │ └── gen_proto.sh # 生成 Protobuf │ ├── docs/ # 文档 │ ├── api.md # API 文档 │ ├── architecture.md # 架构文档 │ └── design/ # 设计文档 │ └── lotto_algorithm.md │ ├── test/ # 测试 │ ├── integration/ # 集成测试 │ └── benchmark/ # 性能测试 │ ├── go.mod ├── go.sum ├── Makefile └── README.md
DDD 四层架构详解 接口层 (Interfaces Layer) 职责 :适配外部请求,转换为应用层可处理的格式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 package handlertype LottoHandler struct { lottoService application.LottoService } func NewLottoHandler (lottoService application.LottoService) *LottoHandler { return &LottoHandler{ lottoService: lottoService, } } func (h *LottoHandler) DrawLotto(c *gin.Context) { var req DrawLottoRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(400 , gin.H{"error" : err.Error()}) return } dto := &dto.DrawLottoDTO{ UserID: req.UserID, ActivityID: req.ActivityID, } result, err := h.lottoService.DrawLotto(c.Request.Context(), dto) if err != nil { c.JSON(500 , gin.H{"error" : err.Error()}) return } c.JSON(200 , gin.H{ "prize_id" : result.PrizeID, "prize_name" : result.PrizeName, "prize_type" : result.PrizeType, }) }
关键点 :
✅ 只负责 HTTP 协议的处理(参数绑定、响应序列化)
✅ 不包含业务逻辑
✅ 调用应用层服务
应用层 (Application Layer) 职责 :编排业务流程,协调多个领域服务。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 package servicetype LottoService interface { DrawLotto(ctx context.Context, dto *dto.DrawLottoDTO) (*dto.DrawLottoResultDTO, error ) } type lottoServiceImpl struct { lottoDomainService domain.LottoDomainService lottoRepo domain.LottoRepository prizeRepo domain.PrizeRepository userClient infrastructure.UserClient lottoCache infrastructure.LottoCache eventProducer infrastructure.LottoEventProducer } func (s *lottoServiceImpl) DrawLotto(ctx context.Context, dto *dto.DrawLottoDTO) (*dto.DrawLottoResultDTO, error ) { if err := s.validateRequest(dto); err != nil { return nil , err } user, err := s.userClient.GetUser(ctx, dto.UserID) if err != nil { return nil , fmt.Errorf("get user failed: %w" , err) } activity, err := s.lottoRepo.GetActivity(ctx, dto.ActivityID) if err != nil { return nil , err } prizes, err := s.prizeRepo.ListPrizesByActivity(ctx, dto.ActivityID) if err != nil { return nil , err } wonPrize, err := s.lottoDomainService.Draw(ctx, user, activity, prizes) if err != nil { return nil , err } lottoRecord := &entity.LottoRecord{ UserID: dto.UserID, ActivityID: dto.ActivityID, PrizeID: wonPrize.ID, DrawTime: time.Now(), } if err := s.lottoRepo.SaveLottoRecord(ctx, lottoRecord); err != nil { return nil , err } event := &LottoDrawnEvent{ UserID: dto.UserID, PrizeID: wonPrize.ID, ActivityID: dto.ActivityID, } s.eventProducer.PublishLottoDrawnEvent(ctx, event) return &dto.DrawLottoResultDTO{ PrizeID: wonPrize.ID, PrizeName: wonPrize.Name, PrizeType: wonPrize.Type, }, nil }
关键点 :
✅ 编排业务流程(校验 → 加载数据 → 调用领域服务 → 保存结果 → 发送事件)
✅ 调用领域服务、仓储、基础设施
✅ 不包含核心业务逻辑(业务逻辑在领域层)
领域层 (Domain Layer) 职责 :核心业务逻辑,领域模型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 package entitytype Activity struct { ID int64 Name string StartTime time.Time EndTime time.Time Status ActivityStatus MaxDraws int } func (a *Activity) IsActive() bool { now := time.Now() return a.Status == ActivityStatusActive && now.After(a.StartTime) && now.Before(a.EndTime) } type Prize struct { ID int64 Name string Type PrizeType Probability float64 Stock int } func (p *Prize) IsAvailable() bool { return p.Stock > 0 } package servicetype LottoDomainService interface { Draw(ctx context.Context, user *User, activity *Activity, prizes []*Prize) (*Prize, error ) } type lottoDomainServiceImpl struct {}func (s *lottoDomainServiceImpl) Draw(ctx context.Context, user *User, activity *Activity, prizes []*Prize) (*Prize, error ) { if !activity.IsActive() { return nil , errors.New("activity is not active" ) } if user.DrawCount >= activity.MaxDraws { return nil , errors.New("exceed max draws" ) } availablePrizes := make ([]*Prize, 0 ) for _, prize := range prizes { if prize.IsAvailable() { availablePrizes = append (availablePrizes, prize) } } if len (availablePrizes) == 0 { return nil , errors.New("no available prizes" ) } wonPrize := s.drawByProbability(availablePrizes) wonPrize.Stock-- return wonPrize, nil } func (s *lottoDomainServiceImpl) drawByProbability(prizes []*Prize) *Prize { totalProbability := 0.0 for _, prize := range prizes { totalProbability += prize.Probability } rand.Seed(time.Now().UnixNano()) randomValue := rand.Float64() * totalProbability cumulativeProbability := 0.0 for _, prize := range prizes { cumulativeProbability += prize.Probability if randomValue <= cumulativeProbability { return prize } } return prizes[len (prizes)-1 ] }
关键点 :
✅ 核心业务逻辑(活动是否有效、抽奖算法)
✅ 领域对象(Entity、ValueObject)
✅ 不依赖外部服务(只处理纯逻辑)
基础设施层 (Infrastructure Layer) 职责 :实现技术细节(数据库、缓存、RPC、MQ)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 package mysqltype lottoRepositoryImpl struct { db *gorm.DB } func NewLottoRepository (db *gorm.DB) domain.LottoRepository { return &lottoRepositoryImpl{db: db} } func (r *lottoRepositoryImpl) GetActivity(ctx context.Context, activityID int64 ) (*entity.Activity, error ) { var po model.ActivityPO if err := r.db.WithContext(ctx).Where("id = ?" , activityID).First(&po).Error; err != nil { return nil , err } return &entity.Activity{ ID: po.ID, Name: po.Name, StartTime: po.StartTime, EndTime: po.EndTime, Status: entity.ActivityStatus(po.Status), MaxDraws: po.MaxDraws, }, nil } func (r *lottoRepositoryImpl) SaveLottoRecord(ctx context.Context, record *entity.LottoRecord) error { po := &model.LottoRecordPO{ UserID: record.UserID, ActivityID: record.ActivityID, PrizeID: record.PrizeID, DrawTime: record.DrawTime, } return r.db.WithContext(ctx).Create(po).Error } package modeltype ActivityPO struct { ID int64 `gorm:"column:id;primaryKey"` Name string `gorm:"column:name"` StartTime time.Time `gorm:"column:start_time"` EndTime time.Time `gorm:"column:end_time"` Status int `gorm:"column:status"` MaxDraws int `gorm:"column:max_draws"` } func (ActivityPO) TableName() string { return "lotto_activity" }
关键点 :
✅ 实现仓储接口(Repository Interface)
✅ 处理数据库模型转换(PO ↔ Entity)
✅ 不暴露技术细节给领域层
Pipeline 在 DDD 中的位置 Pipeline 是跨层的编排机制 ,可以放在 internal/processor/ 和 internal/pipeline/ 目录。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package processortype Processor interface { Process(ctx context.Context, lottoCtx *LottoContext) error } package pipelinetype LottoPipeline struct { processors []processor.Processor } func NewLottoPipeline () *LottoPipeline { return &LottoPipeline{ processors: make ([]processor.Processor, 0 ), } } func (p *LottoPipeline) AddProcessor(proc processor.Processor) { p.processors = append (p.processors, proc) } func (p *LottoPipeline) Execute(ctx context.Context, lottoCtx *LottoContext) error { for _, proc := range p.processors { if err := proc.Process(ctx, lottoCtx); err != nil { return err } } return nil }
Pipeline 与 DDD 的关系 :
Pipeline 是应用层的编排工具
Processor 可以调用领域服务、仓储、基础设施
文件命名规范 Go 文件命名规范
类型
命名规范
示例
实体
{entity}_entity.go 或 {entity}.go
lotto.go, prize.go
仓储接口
{entity}_repository.go
lotto_repository.go
仓储实现
{entity}_repository_impl.go
lotto_repository_impl.go
服务
{service}_service.go
lotto_service.go
Handler
{resource}_handler.go
lotto_handler.go
DTO
{entity}_dto.go
lotto_dto.go
PO(数据库模型)
{entity}_po.go
lotto_po.go
Processor
{purpose}_processor.go
validation_processor.go
测试文件
{file}_test.go
lotto_service_test.go
包命名规范
✅ 全小写,不使用下划线或驼峰
✅ 简短且有意义(handler 而非 http_handler)
✅ 与目录名一致
1 2 3 4 5 6 package handlerpackage httpHandlerpackage http_handler
包设计原则 依赖方向原则 DDD 四层架构的依赖方向:
1 2 3 4 5 Interfaces Layer ──┐ │ Application Layer ─┼──> Domain Layer │ Infrastructure Layer┘
依赖规则 :
✅ 接口层 依赖 应用层
✅ 应用层 依赖 领域层 + 基础设施层
✅ 基础设施层 依赖 领域层 (实现仓储接口)
❌ 领域层 不依赖任何其他层(纯业务逻辑)
接口反转原则(依赖倒置) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package repositorytype LottoRepository interface { GetActivity(ctx context.Context, activityID int64 ) (*entity.Activity, error ) SaveLottoRecord(ctx context.Context, record *entity.LottoRecord) error } package mysqltype lottoRepositoryImpl struct { db *gorm.DB } func NewLottoRepository (db *gorm.DB) repository.LottoRepository { return &lottoRepositoryImpl{db: db} } import "internal/infrastructure/persistence/mysql" func (s *lottoDomainServiceImpl) Draw() { repo := mysql.NewLottoRepository() }
包的职责边界 每个包应该有明确的职责 ,避免”上帝包”(God Package)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package utilsfunc FormatTime () {}func EncryptPassword () {}func SendEmail () {}func CalculateDiscount () {}package timeutilfunc FormatTime () {}package cryptofunc EncryptPassword () {}package emailfunc SendEmail () {}package pricingfunc CalculateDiscount () {}
常见反模式 反模式 1:循环依赖 1 2 3 4 5 6 import "package B" import "package A"
解决方案 :
反模式 2:God Service(上帝服务) 1 2 3 4 5 6 7 8 9 10 type OrderService struct { } func (s *OrderService) CreateOrder() {}func (s *OrderService) CalculatePrice() {}func (s *OrderService) ApplyPromotion() {}func (s *OrderService) SendNotification() {}func (s *OrderService) UpdateInventory() {}
解决方案 :
拆分为多个服务(PricingService, PromotionService, NotificationService)
使用 Pipeline 编排
反模式 3:贫血模型(Anemic Domain Model) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 type Activity struct { ID int64 StartTime time.Time EndTime time.Time } func (s *LottoService) IsActivityActive(activity *Activity) bool { now := time.Now() return now.After(activity.StartTime) && now.Before(activity.EndTime) } type Activity struct { ID int64 StartTime time.Time EndTime time.Time } func (a *Activity) IsActive() bool { now := time.Now() return now.After(a.StartTime) && now.Before(a.EndTime) } func (s *LottoService) DrawLotto(activity *Activity) error { if !activity.IsActive() { return errors.New("activity is not active" ) } }
项目结构演进路径
项目规模
推荐结构
小型项目(< 5000 行)
简单三层(Handler → Service → Repository)
中型项目(5000-20000 行)
DDD 四层架构 + Pipeline
大型项目(> 20000 行)
DDD 四层架构 + Pipeline + 子域拆分
关键点 :
小项目不必过度设计,保持简单
大项目需要清晰的分层和职责边界
随着项目增长逐步演进架构
其他 Clean Code 实践 除了架构模式、设计模式,日常编码中的命名、函数设计、错误处理、注释等细节也至关重要。
命名的艺术 命名是编程中最重要的技能之一 ,好的命名能让代码自解释,减少注释需求。
变量命名原则 见名知意 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func process (u *User, o *Order, p int64 ) { d := o.Total - p if d < 0 { return errors.New("invalid" ) } } func CalculateFinalPrice (user *User, order *Order, discountAmount int64 ) (int64 , error ) { finalPrice := order.TotalAmount - discountAmount if finalPrice < 0 { return 0 , errors.New("discount exceeds total amount" ) } return finalPrice, nil }
使用领域术语 1 2 3 4 5 func GetData (id int64 ) (*Entity, error ) {}func GetFlashSaleActivity (activityID int64 ) (*FlashSaleActivity, error ) {}
变量作用域越大,名称越详细 1 2 3 4 5 6 7 8 9 10 11 for i, item := range items { } var globalFlashSaleActivityCache Cache func ProcessOrder (userID int64 , orderID int64 , calculationContext *PriceCalculationContext) { }
避免误导性命名 1 2 3 4 5 6 7 8 9 10 var orderList *Order var isValidCount int var order *Ordervar validCount int var isValid bool
函数命名原则 动词 + 名词 1 2 3 4 5 6 7 func CalculatePrice () {}func ValidateOrder () {}func GetUser () {}func CreateOrder () {}func UpdateInventory () {}func DeleteCache () {}
布尔函数用 Is/Has/Can/Should 开头 1 2 3 4 5 func IsActive (activity *Activity) bool {}func HasPermission (user *User, resource string ) bool {}func CanRefund (order *Order) bool {}func ShouldRetry (err error ) bool {}
查询函数用 Get/Find/Query/Fetch 1 2 3 4 5 func GetUserByID (userID int64 ) (*User, error ) {} func FindUserByEmail (email string ) (*User, error ) {} func QueryOrdersByUser (userID int64 ) ([]*Order, error ) {} func FetchPriceFromSupplier (itemID int64 ) (int64 , error ) {}
命名反映函数的副作用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func GetUser (userID int64 ) (*User, error ) { user := queryUserFromDB(userID) user.LastAccessTime = time.Now() updateUserToDB(user) return user, nil } func GetUserAndUpdateAccessTime (userID int64 ) (*User, error ) { user := queryUserFromDB(userID) user.LastAccessTime = time.Now() updateUserToDB(user) return user, nil }
常量/枚举命名 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 const ( MAX_RETRY_COUNT = 3 DEFAULT_TIMEOUT = 30 * time.Second CACHE_EXPIRATION_TIME = 5 * time.Minute ) type OrderStatus int const ( OrderStatusPending OrderStatus = 1 OrderStatusPaid OrderStatus = 2 OrderStatusShipped OrderStatus = 3 OrderStatusCompleted OrderStatus = 4 OrderStatusCancelled OrderStatus = 5 ) const ( ErrCodeInvalidParam = 10001 ErrCodeUserNotFound = 10002 ErrCodeOrderNotFound = 10003 ErrCodeInsufficientStock = 10004 )
函数设计的黄金法则 单一职责原则(函数级别) 一个函数只做一件事 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 func ProcessOrder (order *Order) error { if order.UserID == 0 { return errors.New("invalid user" ) } if order.TotalAmount <= 0 { return errors.New("invalid amount" ) } price := order.Subtotal if order.CouponID > 0 { coupon := getCoupon(order.CouponID) price -= coupon.DiscountAmount } order.FinalPrice = price return saveOrder(order) } func ProcessOrder (order *Order) error { if err := ValidateOrder(order); err != nil { return err } if err := CalculateOrderPrice(order); err != nil { return err } return SaveOrder(order) } func ValidateOrder (order *Order) error { if order.UserID == 0 { return errors.New("invalid user" ) } if order.TotalAmount <= 0 { return errors.New("invalid amount" ) } return nil } func CalculateOrderPrice (order *Order) error { price := order.Subtotal if order.CouponID > 0 { coupon := getCoupon(order.CouponID) price -= coupon.DiscountAmount } order.FinalPrice = price return nil } func SaveOrder (order *Order) error { return orderRepo.Save(order) }
参数数量限制 函数参数不超过 3 个 ,超过则使用结构体。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 func CreateOrder ( userID int64 , itemID int64 , quantity int , couponID int64 , addressID int64 , paymentMethod int , deliveryTime time.Time, remark string , ) (*Order, error ) { } type CreateOrderRequest struct { UserID int64 ItemID int64 Quantity int CouponID int64 AddressID int64 PaymentMethod int DeliveryTime time.Time Remark string } func CreateOrder (req *CreateOrderRequest) (*Order, error ) { }
函数长度限制 函数不超过 50 行 ,超过则拆分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 func ProcessFlashSaleOrder (order *Order) error { } func ProcessFlashSaleOrder (ctx context.Context, order *Order) error { fsCtx := &FlashSaleContext{Order: order} pipeline := NewFlashSalePipeline(). AddProcessor(NewValidationProcessor()). AddProcessor(NewActivityDataProcessor(activityService)). AddProcessor(NewItemDataProcessor(itemService)). AddProcessor(NewInventoryProcessor(inventoryService)). AddProcessor(NewPriceCalculationProcessor(priceService)). AddProcessor(NewOrderCreationProcessor(orderService)) return pipeline.Execute(ctx, fsCtx) }
避免标志参数(Flag Argument) 不要用布尔值控制函数行为 ,应该拆分为两个函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 func SaveOrder (order *Order, isAsync bool ) error { if isAsync { go func () { db.Save(order) }() return nil } else { return db.Save(order) } } func SaveOrder (order *Order) error { return db.Save(order) } func SaveOrderAsync (order *Order) error { go func () { db.Save(order) }() return nil }
函数返回值原则 错误处理用多返回值 1 2 3 4 5 6 7 8 9 10 func GetUser (userID int64 ) (*User, error ) { } user, err := GetUser(123 ) if err != nil { return err }
避免返回 nil + nil 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 func FindUser (email string ) (*User, error ) { user := queryUser(email) if user == nil { return nil , nil } return user, nil } func FindUser (email string ) (*User, error ) { user := queryUser(email) if user == nil { return nil , ErrUserNotFound } return user, nil }
区分布尔结果与判定失败 纯内存谓词且输入契约已经保证有效时,可以只返回 bool。若判定可能因依赖失败或输入非法而无法完成,应返回 (bool, error),避免把“无法判定”混成“条件不成立”。两种签名服务于不同契约,不能仅凭返回布尔值就禁止错误。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 func CheckActive (activity *Activity) (bool , error ) { if activity == nil { return false , errors.New("activity is nil" ) } return activity.Status == StatusActive, nil } func IsActive (activity *Activity) bool { return activity.Status == StatusActive } if activity != nil && IsActive(activity) { }
错误处理的统一范式 定义业务错误码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 package errorstype ErrorCode int const ( ErrCodeSuccess ErrorCode = 0 ErrCodeInvalidParam ErrorCode = 10001 ErrCodeUnauthorized ErrorCode = 10002 ErrCodeInternalError ErrorCode = 10003 ErrCodeUserNotFound ErrorCode = 20001 ErrCodeUserBlocked ErrorCode = 20002 ErrCodeOrderNotFound ErrorCode = 30001 ErrCodeOrderCancelled ErrorCode = 30002 ErrCodeItemNotFound ErrorCode = 40001 ErrCodeInsufficientStock ErrorCode = 40002 ) var errorMessages = map [ErrorCode]string { ErrCodeSuccess: "Success" , ErrCodeInvalidParam: "Invalid parameter" , ErrCodeUnauthorized: "Unauthorized" , ErrCodeInternalError: "Internal error" , ErrCodeUserNotFound: "User not found" , ErrCodeUserBlocked: "User is blocked" , ErrCodeOrderNotFound: "Order not found" , ErrCodeOrderCancelled: "Order is cancelled" , ErrCodeItemNotFound: "Item not found" , ErrCodeInsufficientStock: "Insufficient stock" , } func (e ErrorCode) Message() string { if msg, ok := errorMessages[e]; ok { return msg } return "Unknown error" }
自定义业务错误 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 package errorsimport "fmt" type BusinessError struct { Code ErrorCode Message string Err error } func (e *BusinessError) Error() string { if e.Err != nil { return fmt.Sprintf("[%d] %s: %v" , e.Code, e.Message, e.Err) } return fmt.Sprintf("[%d] %s" , e.Code, e.Message) } func (e *BusinessError) Unwrap() error { return e.Err } func New (code ErrorCode, message string ) *BusinessError { return &BusinessError{ Code: code, Message: message, } } func Wrap (code ErrorCode, err error ) *BusinessError { return &BusinessError{ Code: code, Message: code.Message(), Err: err, } } var ( ErrUserNotFound = New(ErrCodeUserNotFound, "User not found" ) ErrInsufficientStock = New(ErrCodeInsufficientStock, "Insufficient stock" ) ErrOrderNotFound = New(ErrCodeOrderNotFound, "Order not found" ) )
错误处理最佳实践 及早返回(Early Return) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 func ProcessOrder (order *Order) error { if order != nil { if order.UserID > 0 { if order.TotalAmount > 0 { return saveOrder(order) } else { return errors.New("invalid amount" ) } } else { return errors.New("invalid user" ) } } else { return errors.New("order is nil" ) } } func ProcessOrder (order *Order) error { if order == nil { return errors.New("order is nil" ) } if order.UserID <= 0 { return errors.New("invalid user" ) } if order.TotalAmount <= 0 { return errors.New("invalid amount" ) } return saveOrder(order) }
错误包装(Error Wrapping) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 func GetUser (userID int64 ) (*User, error ) { user, err := userRepo.FindByID(userID) if err != nil { return nil , fmt.Errorf("get user from repo failed: %w" , err) } return user, nil } user, err := GetUser(123 ) if errors.Is(err, sql.ErrNoRows) { }
统一错误响应 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 func (h *OrderHandler) CreateOrder(c *gin.Context) { order, err := h.orderService.CreateOrder(ctx, req) if err != nil { var bizErr *errors.BusinessError if errors.As(err, &bizErr) { c.JSON(400 , gin.H{ "code" : bizErr.Code, "message" : bizErr.Message, }) return } c.JSON(500 , gin.H{ "code" : errors.ErrCodeInternalError, "message" : "Internal error" , }) return } c.JSON(200 , gin.H{ "code" : 0 , "data" : order, }) }
注释的正确打开方式 注释原则
好的代码 > 好的注释 > 坏的注释 > 没有注释
代码即文档(优先用命名表达意图) 1 2 3 4 5 6 7 8 9 10 11 if u.IsNew && u.OrdCnt == 0 { d = 2000 } if user.IsNewUser && user.OrderCount == 0 { discountAmount = NEW_USER_DISCOUNT_AMOUNT }
只注释”为什么”,不注释”是什么” 1 2 3 4 5 6 7 8 9 10 user := getUser(userID) cache.WarmUp(popularItemIDs) price := calculatePriceV1(order)
注释复杂算法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func findMedianSortedArrays (nums1, nums2 []int ) float64 { } func drawPrize (prizes []*Prize) *Prize { }
函数注释(godoc 风格) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func GetUser (userID int64 ) (*User, error ) { }
TODO/FIXME/HACK 注释 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 func QueryOrders (userID int64 ) ([]*Order, error ) { } func UpdateInventory (itemID int64 , quantity int ) error { } func GetPriceWithFallback (itemID int64 ) (int64 , error ) { price, err := priceService.GetPrice(itemID) if err != nil { return cache.GetPrice(itemID), nil } return price, nil }
不要留下被注释掉的代码 1 2 3 4 5 6 7 8 9 10 11 12 func CalculatePrice (order *Order) int64 { price := order.Subtotal return price } func CalculatePrice (order *Order) int64 { return order.Subtotal }
代码审查 Checklist 在 Code Review 时,可以参考以下 Checklist:
命名检查
函数检查
错误处理检查
注释检查
性能检查
重构实战案例 Fowler 将重构定义为在保持可观察行为的前提下改善内部结构。[9] 下面三个电商教学案例(下单、计价、库存)用 Go 示意如何从坏味道走向可测、可扩展的结构;验收应核对业务行为,而不只比较函数行数。
案例 1:千行函数重构为 Pipeline 重构前 历史上存在一个约 1500 行的 CreateOrder,下面是其逻辑骨架 (约 40 行),用注释标出 8 个步骤;真实代码每步内含大量 SQL、RPC 与分支——属于典型的「上帝函数」。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 func (s *OrderService) CreateOrder(ctx context.Context, req *CreateOrderRequest) (*Order, error ) { if err := s.validateRequest(req); err != nil { return nil , err } user, err := s.userClient.Verify(ctx, req.UserID) if err != nil { return nil , err } if err := s.inventory.Reserve(ctx, req.SKUs); err != nil { return nil , err } price, err := s.pricing.Calc(ctx, req) if err != nil { return nil , err } discount, err := s.promo.Apply(ctx, user, req, price) if err != nil { return nil , err } if err := s.risk.Check(ctx, user, req, price, discount); err != nil { return nil , err } order, err := s.repo.InsertOrder(ctx, buildOrder(user, req, price, discount)) if err != nil { return nil , err } _ = s.notify.Send(ctx, order) return order, nil }
问题分析
维度
表现
SRP
一个函数同时负责校验、外部依赖编排、持久化与通知,修改任一环节都可能波及其他步骤。
圈复杂度
各步内部大量 if/switch 与错误分支,整体约 45 ,难以穷举路径。
测试
必须 mock 整条依赖链,单测脆弱;**覆盖率约 15%**,多为集成测试碰运气。
认知负荷
新人必须「读懂整个函数」才能安全改一行,Code Review 成本极高。
重构步骤
识别步骤边界 将上述 8 步各自视为独立「处理器」,命名清晰、顺序固定:ValidateProcessor、UserVerifyProcessor、InventoryReserveProcessor、PriceCalcProcessor、PromoProcessor、RiskProcessor、PersistOrderProcessor、NotifyProcessor。
定义 OrderContext 用上下文承载输入、中间态与输出,避免在 Pipeline 里散落一堆局部变量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 type OrderContext struct { Input struct { Req *CreateOrderRequest } Intermediate struct { User *User Price Money Discount Money } Output struct { Order *Order } Err error }
提取 Processor(示例:校验一步) 每个 Processor 只做一件事,签名统一,便于单测与替换顺序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 type OrderProcessor interface { Name() string Process(ctx context.Context, oc *OrderContext) error } type ValidateProcessor struct {}func (ValidateProcessor) Name() string { return "validate" }func (ValidateProcessor) Process(ctx context.Context, oc *OrderContext) error { if oc.Input.Req == nil || len (oc.Input.Req.SKUs) == 0 { return ErrInvalidRequest } return nil }
组装 Pipeline 将处理器按业务顺序注册;执行时逐个 Process,出错即短路。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 func NewOrderCreatePipeline ( v ValidateProcessor, u UserVerifyProcessor, inv InventoryReserveProcessor, p PriceCalcProcessor, pr PromoProcessor, r RiskProcessor, ps PersistOrderProcessor, n NotifyProcessor, ) *Pipeline { return &Pipeline{ steps: []OrderProcessor{v, u, inv, p, pr, r, ps, n}, } } type Pipeline struct { steps []OrderProcessor } func (p *Pipeline) Run(ctx context.Context, oc *OrderContext) error { for _, step := range p.steps { if err := step.Process(ctx, oc); err != nil { oc.Err = err return err } } return nil }
重构后效果
指标
重构前
重构后
圈复杂度(单函数/单步)
约 45(整函数)
典型每步 ≤5
测试覆盖率
约 15%
可达 **85%**(每 Processor 独立 mock)
新增业务步骤成本
改千行函数、牵一发而动全身
新增 1 个文件(Processor)+ Pipeline 注册 1 行
认知负荷
读懂整个 CreateOrder
读懂单个 Processor 即可安全修改
案例 2:if-else 地狱重构为策略模式 重构前 计价逻辑按商品品类分支堆砌,每来一个新品类就要改这个函数,违反开闭原则。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 type CategoryID string const ( CatTopup CategoryID = "topup" CatHotel CategoryID = "hotel" CatFlight CategoryID = "flight" CatDeal CategoryID = "deal" CatVoucher CategoryID = "voucher" ) func (s *PricingService) CalculatePrice(ctx context.Context, order *Order) (Money, error ) { if order.Category == CatTopup { return s.calcTopup(ctx, order) } else if order.Category == CatHotel { return s.calcHotel(ctx, order) } else if order.Category == CatFlight { return s.calcFlight(ctx, order) } else if order.Category == CatDeal { return s.calcDeal(ctx, order) } else if order.Category == CatVoucher { return s.calcVoucher(ctx, order) } return Zero, ErrUnsupportedCategory }
(真实代码里每个 calcXxx 前往往还有一层 if 做子类型与币种,此处省略。)
问题分析
OCP :每增加一个 CategoryID,必须修改 CalculatePrice 的 if-else 链,合并冲突与回归风险集中。
可测性 :要对「酒店」计价做单测,仍需构造能走进该分支链的完整订单,边界用例与 mock 成本高。
团队协作 :不同业务线改同一文件,Review 粒度粗,容易误伤其他品类。
重构步骤
定义策略接口 统一入口:Calculate(ctx, order) (Money, error)。
1 2 3 type PriceCalculator interface { Calculate(ctx context.Context, order *Order) (Money, error ) }
具体实现(示例:酒店) 酒店可单独测,不依赖其他品类的分支。
1 2 3 4 5 6 7 8 9 10 11 12 type HotelPriceCalculator struct { rateAPI HotelRateClient } func (h HotelPriceCalculator) Calculate(ctx context.Context, order *Order) (Money, error ) { nights := order.Nights base, err := h.rateAPI.NightlyRate(ctx, order.HotelID, order.CheckIn) if err != nil { return Zero, err } return base.MulInt(nights), nil }
构建注册表 用 map[CategoryID]PriceCalculator 做查找,初始化时在 composition root 注入。
1 2 3 4 5 6 7 func NewPricingService (calcs map [CategoryID]PriceCalculator) *PricingService { return &PricingService{calcs: calcs} } type PricingService struct { calcs map [CategoryID]PriceCalculator }
用注册表替代 if-else 链
1 2 3 4 5 6 7 func (s *PricingService) CalculatePrice(ctx context.Context, order *Order) (Money, error ) { calc, ok := s.calcs[order.Category] if !ok { return Zero, ErrUnsupportedCategory } return calc.Calculate(ctx, order) }
重构后效果 新增品类 = 1 个新文件(实现 PriceCalculator)+ 注册表处增加 1 行 (例如 calcs[CatNewThing] = NewThingCalculator(...))。CalculatePrice 本身不再随品类膨胀,符合对扩展开放、对修改关闭。
案例 3:上下文爆炸重构为 Context Pattern 重构前 参数在调用链上层层透传,函数签名冗长,任何一层增参都会波及上下游。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 func (s *CheckoutService) PlaceOrder( ctx context.Context, userID string , cartID string , region string , currency string , clientIP string , deviceID string , requestID string , traceID string , ) error { return s.orchestrator.RunCheckout(ctx, userID, cartID, region, currency, clientIP, deviceID, requestID, traceID) } func (o *Orchestrator) RunCheckout( ctx context.Context, userID, cartID, region, currency, clientIP, deviceID, requestID, traceID string , ) error { return o.reserveInventory(ctx, userID, cartID, region, currency, clientIP, deviceID, requestID, traceID) } func (o *Orchestrator) reserveInventory( ctx context.Context, userID, cartID, region, currency, clientIP, deviceID, requestID, traceID string , ) error { return o.payment.Charge(ctx, userID, cartID, region, currency, clientIP, deviceID, requestID, traceID) }
重构步骤
按职责分组参数
Input :请求侧不变量(UserID、CartID、Region、Currency 等)。
Intermediate :流程中写入的库存预留 ID、支付单号等(可在各阶段填充)。
Output :最终订单 ID、错误等。
定义 ProcessContext
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 type ProcessContext struct { Input struct { UserID string CartID string Region string Currency string ClientIP string DeviceID string RequestID string TraceID string } Intermediate struct { ReservationID string PaymentID string } Output struct { OrderID string } }
各层只接受 *ProcessContext 新增观测字段或中间态时,多数情况只改 struct 字段,不改每层函数签名。
1 2 3 4 5 6 7 8 9 10 11 func (s *CheckoutService) PlaceOrder(ctx context.Context, pc *ProcessContext) error { return s.orchestrator.RunCheckout(ctx, pc) } func (o *Orchestrator) RunCheckout(ctx context.Context, pc *ProcessContext) error { return o.reserveInventory(ctx, pc) } func (o *Orchestrator) reserveInventory(ctx context.Context, pc *ProcessContext) error { return o.payment.Charge(ctx, pc) }
重构后效果
参数数量 :从调用链上累计 12+ 个标量参数 (每层重复罗列)收敛为 **1 个 *ProcessContext**。
扩展性 :加字段优先在 struct 上完成,避免「改签名雪崩」。
注意 :Context Pattern 这里是流程上下文 struct ,不要与 context.Context 混淆;两者可并存(第一个参数仍可保留 ctx context.Context)。
性能优化与监控 Pipeline 与 Context Pattern 把业务拆清楚之后,下一关是在高 QPS 下仍保持稳定延迟 ,以及出问题能秒级定位 。本节从「减分配、提并行、控超时、批写」到指标、链路、日志与告警,给出一套可落地的 Go 侧做法。
性能优化策略 sync.Pool 复用 ProcessContext 高频路径里若每次 Run 都 new(ProcessContext),小对象会推高 allocation rate 与 GC 压力 。sync.Pool 适合生命周期短、可重置 的缓冲区或上下文载体:在 Get 后清零字段,在 Put 前再次归零,避免脏数据泄漏。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 import ( "context" "sync" ) var processContextPool = sync.Pool{ New: func () any { return &ProcessContext{} }, } func acquireProcessContext () *ProcessContext { pc := processContextPool.Get().(*ProcessContext) *pc = ProcessContext{} return pc } func releaseProcessContext (pc *ProcessContext) { if pc == nil { return } *pc = ProcessContext{} processContextPool.Put(pc) } func (p *Pipeline) RunPooled(ctx context.Context, seed *ProcessContext) error { pc := acquireProcessContext() defer releaseProcessContext(pc) if seed != nil { *pc = *seed } for _, proc := range p.processors { if err := proc.Process(ctx, pc); err != nil { return err } } return nil }
要点:Pool 不保证保留任何已放入对象,也不保证 Get 返回某个指定对象 ;只用于性能优化,不能当缓存存业务唯一态。重置用值赋值 *pc = ProcessContext{} 比逐字段置零更不容易漏字段。
并行 Stage:errgroup 扇出 / 扇入 当多个 Processor 彼此无数据依赖 (例如并行读用户、优惠券、库存快照),可用 errgroup 等待退出并统一错误处理;默认不限制并发,可通过 SetLimit 设置上限。下面的共享 *OrderContext 要求输入不可变,且各处理器仅写各自拥有的字段,不共享可变 map、slice 或日志集合。否则应改为任务返回独立结果,等待后串行合并。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import ( "context" "golang.org/x/sync/errgroup" ) type ParallelBundle struct { userProc OrderProcessor couponProc OrderProcessor stockProc OrderProcessor } func (b *ParallelBundle) Process(ctx context.Context, pc *OrderContext) error { g, ctx := errgroup.WithContext(ctx) g.Go(func () error { return b.userProc.Process(ctx, pc) }) g.Go(func () error { return b.couponProc.Process(ctx, pc) }) g.Go(func () error { return b.stockProc.Process(ctx, pc) }) return g.Wait() }
errgroup.WithContext 在首个任务返回错误或 Wait 返回时取消派生 Context,但不强制结束其他 goroutine;任务必须配合取消,Wait 才能及时完成。若某步不应因兄弟失败而取消,可用同一个父请求 Context 配合普通 errgroup.Group,或拆阶段设计;仍须保留请求取消和总预算。
超时:按 Stage 包裹与优雅降级 延迟 SLO 可用整链延迟阈值与达标比例表达,对内还需要每一跳的预算。context.WithTimeout 只发出取消信号,下面同步等待的 Process 必须支持取消才能及时返回;上层再按副作用与业务政策决定重试、熔断或降级。[4]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import ( "context" "errors" "fmt" "time" ) func (p *Pipeline) RunWithTimeout(ctx context.Context, pc *ProcessContext, perStage time.Duration) error { for _, proc := range p.processors { stageCtx, cancel := context.WithTimeout(ctx, perStage) err := proc.Process(stageCtx, pc) if err == nil { err = stageCtx.Err() } cancel() if err != nil { if errors.Is(err, context.DeadlineExceeded) { return fmt.Errorf("stage %s exceeded %s: %w" , proc.Name(), perStage, err) } return fmt.Errorf("stage %s: %w" , proc.Name(), err) } } return nil }
优雅降级示例:计价超时则使用缓存价或默认折扣(业务允许的前提下),并打标 pc.Degraded = true 供监控与对账。
批处理写库:聚合再 Flush 把多条事件放进同一事务可以减少提交次数。下面以 MySQL 8.4 的 InnoDB 表为例,演示批次所有权、失败保留与重复提交处理;仍逐条执行 SQL,不是一次多行 INSERT,不能据此宣称减少了每条语句的网络往返。重复键处理依据数据库唯一约束。[10]
事件 ID 必须在业务事件产生时确定,同一次事件的重试复用 ID、订单 ID 与原始载荷。这里要求表只有 event_id 这一项唯一约束;同 ID 的内容不同应报冲突,不能用覆盖旧内容来“去重”。
1 2 3 4 5 CREATE TABLE order_events ( event_id VARBINARY (128 ) NOT NULL PRIMARY KEY, order_id BIGINT NOT NULL , payload LONGBLOB NOT NULL ) ENGINE= InnoDB;
Add 成功只表示事件进入当前进程的内存队列。Flush 成功表示本次取得的批次已经确认提交,不包含刷盘期间新加入的事件。两个锁分别保护队列和刷盘所有权,使追加操作可以继续进行,同时只有一个 Flush 推进队首。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 import ( "bytes" "context" "database/sql" "fmt" "sync" ) type OrderEvent struct { EventID string OrderID int64 Payload []byte } type BatchedOrderRepo struct { db *sql.DB mu sync.Mutex flushMu sync.Mutex buf []OrderEvent } func (r *BatchedOrderRepo) Add(ctx context.Context, ev OrderEvent) error { if err := ctx.Err(); err != nil { return err } if len (ev.EventID) == 0 || len (ev.EventID) > 128 { return fmt.Errorf("event ID must contain 1 to 128 bytes" ) } ev.Payload = append ([]byte {}, ev.Payload...) r.mu.Lock() r.buf = append (r.buf, ev) r.mu.Unlock() return nil } func (r *BatchedOrderRepo) Flush(ctx context.Context) error { r.flushMu.Lock() defer r.flushMu.Unlock() r.mu.Lock() if len (r.buf) == 0 { r.mu.Unlock() return nil } batch := append ([]OrderEvent(nil ), r.buf...) r.mu.Unlock() tx, err := r.db.BeginTx(ctx, nil ) if err != nil { return err } defer tx.Rollback() stmt, err := tx.PrepareContext(ctx, ` INSERT INTO order_events(event_id, order_id, payload) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE event_id = event_id` ) if err != nil { return err } defer stmt.Close() for _, ev := range batch { if _, err := stmt.ExecContext(ctx, ev.EventID, ev.OrderID, ev.Payload); err != nil { return err } var orderID int64 var payload []byte if err := tx.QueryRowContext(ctx, `SELECT order_id, payload FROM order_events WHERE event_id = ? FOR UPDATE` , ev.EventID).Scan(&orderID, &payload); err != nil { return err } if orderID != ev.OrderID || !bytes.Equal(payload, ev.Payload) { return fmt.Errorf("event ID conflict: %s" , ev.EventID) } } if err := tx.Commit(); err != nil { return err } r.mu.Lock() for i := range batch { r.buf[i] = OrderEvent{} } r.buf = r.buf[len (batch):] r.mu.Unlock() return nil }
开启事务、准备语句、执行或提交报错,都不会移除内存中的批次。若数据库已经提交但确认响应丢失,再次 Flush 会按相同事件 ID 命中已有记录并核对内容;确认成功后才移除队首。不同内容复用 ID 属于永久冲突,应报警并人工核对或隔离,不应无限重试。这里的去重只覆盖事件表写入,不保证支付、发货等外部副作用只执行一次。
这是内存批处理示例:进程崩溃仍可能丢失已入队但未持久化的事件。业务成功响应若承诺事件可恢复,应使用与业务数据同事务的 Outbox 或其他持久化队列,不能把 Add 的返回值当作持久化确认。生产实现还需由调度器按条数或时间触发 Flush,设置队列上限与背压、重试预算和优雅关停;关停期间先停止入队,再刷盘。批次大小、逐条校验的开销与吞吐量应在目标环境测量。
监控与可观测性 Metrics:Stage 耗时与成功 / 失败计数 Prometheus 侧用 Histogram 看 P50/P99,用 Counter 看吞吐与错误率。下面在 Pipeline 外包一层中间件,统一注册与打点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 import ( "time" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" ) var ( stageDuration = promauto.NewHistogramVec( prometheus.HistogramOpts{ Name: "pipeline_stage_duration_seconds" , Help: "Wall time per pipeline stage" , Buckets: prometheus.ExponentialBuckets(0.001 , 2 , 16 ), }, []string {"stage" }, ) stageOutcome = promauto.NewCounterVec( prometheus.CounterOpts{ Name: "pipeline_stage_total" , Help: "Pipeline stage outcomes" , }, []string {"stage" , "result" }, ) ) type MetricsProcessor struct { inner OrderProcessor } func (m MetricsProcessor) Name() string { return m.inner.Name() }func (m MetricsProcessor) Process(ctx context.Context, pc *OrderContext) error { start := time.Now() err := m.inner.Process(ctx, pc) stageDuration.WithLabelValues(m.inner.Name()).Observe(time.Since(start).Seconds()) if err != nil { stageOutcome.WithLabelValues(m.inner.Name(), "error" ).Inc() return err } stageOutcome.WithLabelValues(m.inner.Name(), "ok" ).Inc() return nil }
分布式追踪:每 Stage 一个 Span OpenTelemetry 将「Pipeline 第几步」映射为 span,便于在 Jaeger / Tempo 里看瀑布图。tracer.Start 务必 defer span.End(),并用 span.RecordError 记录错误。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import ( "context" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/codes" ) var tracer = otel.Tracer("order/pipeline" )type TraceProcessor struct { inner OrderProcessor } func (t TraceProcessor) Name() string { return t.inner.Name() }func (t TraceProcessor) Process(ctx context.Context, pc *OrderContext) error { ctx, span := tracer.Start(ctx, "pipeline." +t.inner.Name()) defer span.End() err := t.inner.Process(ctx, pc) if err != nil { span.RecordError(err) span.SetStatus(codes.Error, err.Error()) return err } return nil }
结构化日志:trace_id、stage、duration slog 与 context 中的 trace_id(由 OTel 或网关注入)结合,可在日志平台按链路检索。中间件统一打一条「阶段结束」日志。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import ( "context" "log/slog" "time" ) type LogProcessor struct { inner OrderProcessor log *slog.Logger } func (l LogProcessor) Name() string { return l.inner.Name() }func (l LogProcessor) Process(ctx context.Context, pc *OrderContext) error { start := time.Now() err := l.inner.Process(ctx, pc) traceID, _ := ctx.Value("trace_id" ).(string ) l.log.InfoContext(ctx, "pipeline_stage" , slog.String("trace_id" , traceID), slog.String("stage_name" , l.inner.Name()), slog.Duration("duration" , time.Since(start)), slog.String("result" , resultString(err)), ) return err } func resultString (err error ) string { if err != nil { return "error" } return "ok" }
告警规则(示例)
告警项
条件(示例)
含义
Stage P99 过高
histogram_quantile(0.99, rate(pipeline_stage_duration_seconds_bucket[5m])) > 0.5
单阶段 P99 超过 500ms
错误率
sum(rate(pipeline_stage_total{result="error"}[5m])) / sum(rate(pipeline_stage_total[5m])) > 0.05
错误率超过 5%
Goroutine 泄漏
go_goroutines > 10000
协程数异常,可能阻塞或泄漏
阈值需按业务与实例规格调优;错误率告警建议排除已知降级路径或配合 burn rate。
团队落地建议 Clean Code 与 Pipeline 重构不仅是个人习惯,更是团队契约 :Review 标准、说服资源、控制风险,三者缺一就容易「一次热情、长期回潮」。
Code Review Checklist 下面是一份紧凑版 清单,适合贴在 MR 模板或团队 Wiki;完整维度(架构边界、聚合、CQRS 等)见专文。
编码层(5 条)
函数职责与分支是否可理解?80 行 可作为团队提醒值,不能代替复杂度和修改成本的判断。
命名是否反映业务语义 (动词 + 领域对象),而非实现细节?
错误是否包装上下文 (fmt.Errorf("...: %w", err)),避免裸返回?
修改是否违背 SOLID 中与本改动最相关的一条(尤其是 SRP、DIP)?
嵌套是否控制在 3 层 以内,能否用早返回或小函数压平?
设计层(5 条)
新增依赖是否指向内侧抽象 (接口在调用方 / 领域侧),而非 concrete 泄漏?
是否尊重 聚合边界 (不变式、事务范围、ID 引用而非对象图乱连)?
读写 / 领域 / 基础设施 是否仍分离,是否出现「为了省事」的跨层调用?
选用的模式(Pipeline、策略、规则引擎)是否与复杂度匹配 ,没有过度设计?
是否可测 :关键路径能否用 fake / mock 在单测覆盖,而不必起全栈?
完整版检查清单见 工程评审清单:架构、设计、代码与上线检查 。
如何说服团队重构 ROI 量化 用「缺陷密度下降 × 单次修复成本」估算节省。示意(数字为教学假设):
指标
重构前
重构后(目标)
说明
生产缺陷 / 千行 / 年
8
4
流水线 + 小函数后,回归面缩小
年均相关缺陷数
40
20
50 万行业务代码量级示意
单次修复成本(人日)
0.5
0.5
含定位、修复、发布
年节省人日
—
10
((40-20) \times 0.5)
再叠加 需求交付周期 、新人上手周数 ,用表格对齐管理层语言,比「代码很臭」有效得多。
Boy Scout Rule(童子军规则) 约定:每个 PR 顺带改善一小块 ——命名、抽一个函数、补两条测试——不单独开「大重构项目」也能复利。
Before / After 指标表(示例)
维度
Before
After
月均与订单域相关的线上 bug
12
6
中等需求从开发到上线的平均人日
9
6
新人读懂下单主路径所需时间
10 天
4 天
重构的风险控制 Feature flag:按配置切换实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 type RuntimeFlags struct { UsePipelineV2 bool } type CheckoutService struct { flags RuntimeFlags pipelineV1 *Pipeline pipelineV2 *Pipeline } func (s *CheckoutService) Run(ctx context.Context, pc *ProcessContext) error { if s.flags.UsePipelineV2 { return s.pipelineV2.Run(ctx, pc) } return s.pipelineV1.Run(ctx, pc) }
配置来自远程配置中心或环境变量,默认关闭新路径 ,观察指标后再放量。
Canary:双跑比对结果 影子计算应针对同一不可变输入快照运行新旧纯计算,保持旧结果对外,并记录差异。不能重复执行真实库存预占、扣款、发券或消息发布;需要验证这些环节时使用隔离环境或无副作用适配器。灰度切流则是在少量真实请求上只选择一条实现,两种验证方式不能混用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 func (s *PricingService) Quote(ctx context.Context, req *QuoteRequest) (Money, error ) { if !s.flags.CanaryNewPricing { return s.legacy.Quote(ctx, req) } snapshot, err := s.loadSnapshot(ctx, req) if err != nil { return Money{}, err } oldVal, oldErr := s.legacy.Calculate(ctx, snapshot) shadowCtx, cancel := context.WithTimeout(ctx, s.shadowBudget) defer cancel() newVal, newErr := s.modern.Calculate(shadowCtx, snapshot) if newErr == nil { newErr = shadowCtx.Err() } if !equalMoney(newVal, oldVal) || errorKind(newErr) != errorKind(oldErr) { s.log.Warn("pricing_canary_mismatch" , "new" , newVal, "old" , oldVal) } return oldVal, oldErr }
(生产上可逐步改为新逻辑为主,此处强调比对与观测 优先。)
以上为接口契约示意,Calculate 必须是无副作用计算;同步影子执行仍会增加用户等待时间,预算应计入请求 SLO。高成本场景可用有界任务队列处理脱敏快照。金额应按统一币种、精度和舍入政策精确比较;生产验证还需比较行分摊、命中规则、输入版本和拒绝原因,不能只看总额或用浮点容差掩盖差异。
测试覆盖率门禁 教学示例可约定本轮重构触及的包行覆盖率 > 80%,也可采用基线与增量策略;具体门槛由团队按风险设定。覆盖率只说明代码被执行,不能证明断言充分。合并证据还应覆盖业务不变量、异常与取消、重复执行、并发冲突和新旧行为差异。
回滚程序(Checklist)
关闭 Feature flag 或切回旧 Deployment,确认流量已回到旧版本(Ingress / 配置中心 / 发布平台二次确认)。
验证核心监控 :错误率、P99、业务成功率恢复至发布前基线 ± 阈值。
记录事故单 :保留时间线、diff、指标截图,复盘是数据问题、边界遗漏还是发布节奏问题,再决定是否二次上线。
总结与展望 核心要点回顾
章
一句话带走
一、痛点画像
复杂业务之苦在认知负荷、变更成本与线上风险的三重叠加。
二、Clean Code 标准
可读性优先,命名与结构服务于读者而非作者。
三、核心原则
SRP、OCP、LSP、ISP、DIP 是拆模块与依赖方向的罗盘。
四、Pipeline
顺序阶段 + 统一上下文,是编排长流程的首选骨架。
五、Context Pattern
用显式上下文对象收拢参数与中间态,消灭长参数列表。
六、设计模式
在真实分支与扩展点上用模式,而不是为了「像教科书」。
七、规则引擎
规则与代码解耦,适合高频变更的策略与活动逻辑。
八、代码组织
按领域与层次分包,依赖单向向内。
九、其他实践
注释、错误码、测试与风格细节决定长期可维护性。
十、重构案例
大函数 → Pipeline / Context,是电商域最常见的落地路径。
十一、性能与可观测
池化、并行、超时、批写 + 指标追踪日志告警,闭环运维。
十二、团队落地
Review 清单、ROI 叙事与 flag / canary / 覆盖率 / 回滚控风险。
学习路径建议
Junior(0–2 年) 顺序建议:命名 → 函数分解 → 错误处理与边界 。 书目:《Clean Code》(Robert C. Martin)
Mid(2–5 年) SOLID → 设计模式 → Pipeline / 重构手法 。 书目:《设计模式》(GoF)、《Refactoring》(Martin Fowler)
Senior(5 年+) DDD → Clean Architecture → CQRS / 事件驱动 。 书目:《Domain-Driven Design》(Eric Evans)、《Clean Architecture》(Robert C. Martin)
flowchart LR
J[Junior Naming / Functions / Errors] --> M[Mid SOLID / Patterns / Pipeline]
M --> S[Senior DDD / Clean Arch / CQRS]
与本专题其他篇目的衔接 认知升级可以概括为三层:代码级 (函数与命名)、模块级 (边界、依赖方向、聚合)、系统级 (上下文映射、限界上下文、读写分离与演进式架构)。Clean Code 解决「这一行好不好懂」;Clean Architecture 与 DDD 回答「这一块该不该存在、跟谁说话、如何独立演进」。
建议先阅读 Clean Architecture、DDD 与 CQRS:职责边界与组合设计 建立分层与读写模型的判断框架,再用本文练习实现与重构。领域驱动设计:从方法论到电商计价实践 用电商案例展开战略与战术建模;工程评审清单:架构、设计、代码与上线检查 提供各阶段检查项。
参考资料
Implementing Domain-Driven Design ;Vaughn Vernon,2013;出版社书籍页面,核验于 2026-10-12。
Domain-Driven Design: Tackling Complexity in the Heart of Software ;Eric Evans,2003;作者机构的 DDD 资料入口。
Clean Code: A Handbook of Agile Software Craftsmanship ;Robert C. Martin,2008;出版社书籍页面,核验于 2026-10-12。
context package ;Go Authors,Go 标准库文档;核验于 2026-10-12。
Timeouts, retries, and backoff with jitter ;AWS Builders’ Library;核验于 2026-10-12。
sync.Pool ;Go Authors,Go 标准库文档;核验于 2026-10-12。
Design Patterns: Elements of Reusable Object-Oriented Software ;Erich Gamma、Richard Helm、Ralph Johnson、John Vlissides;Addison-Wesley,1994。
gengine ;bilibili,项目仓库;规则 DSL 与执行机制说明;核验于 2026-10-12。
Refactoring ;Martin Fowler,作者维护的重构定义与书籍入口。
INSERT … ON DUPLICATE KEY UPDATE Statement ;Oracle,MySQL 8.4 Reference Manual;核验于 2026-10-12。
最后的话 :
Clean Code 不是一蹴而就的,而是一个持续改进的过程。从简单的命名规范开始,逐步应用设计模式,最终形成团队的编码规范。
记住:好的代码是重构出来的,不是一次写出来的 。
希望这份指南能帮助你在复杂业务中写出更优雅、更易维护的代码!