找回密码
 立即注册
首页 业界区 业界 Go 重构案例分享:订单创建逻辑重构

Go 重构案例分享:订单创建逻辑重构

莅耸 2025-7-4 08:30:28
背景:从 PHP (Laravel) 到 Go 的代码迁移



  • 原 PHP (Laravel) 实现思路:核心模式: “行为管道” (Behavior Pipeline)。
  • 如何工作: 将订单创建拆分成多个独立的小任务 (如:请求限流、素材验证、创建订单、调用支付、埋点)。每个任务是一个实现了特定接口 (BehaviorOrderCreateInterface) 的类。
  • 执行过程: 将这些任务类注册到一个列表中。创建订单时,按顺序执行列表里每个任务的 handle 方法。
  • 状态共享: 每个任务在执行时,能访问和修改同一个“订单创建服务”对象 (OrderCreateService),通过它传递数据和状态(比如订单创建后,埋点任务需要用到订单ID)。
  • 异常处理: 使用 try-catch 捕获异常并进行事务回滚。

makeOrder 创建订单方法


实例化OrderCreateService类后,通过registerBehavior通过 registerBehavior 方法将行为类注册。
最后执行create方法
  1. /**
  2.      * 生成订单
  3.      * @param $params
  4.      * @return string[]
  5.      * @date 2023/6/6 13:30
  6.      * @throws \Exception
  7.      */
  8.     public function makeOrder($params): array
  9.     {
  10.         $db = DB::connection(DbEnum::MYSQL_RELEBOOK);
  11.         $db->beginTransaction();
  12.         try {
  13.             $data = (new OrderCreateService($params))->registerBehavior(
  14.                 function () {
  15.                     return new RequestLimit();
  16.                 },
  17.                 function () {
  18.                     return new ResValidate();
  19.                 },
  20.                 function () {
  21.                     return new Order();
  22.                 },
  23.                 function () {
  24.                     return new Payment();
  25.                 },
  26.                 function () {
  27.                     return new OrderBuried();
  28.                 },
  29.             )->create();
  30.             $db->commit();
  31.             return $data;
  32.         } catch (\LogicException $logicException) {
  33.             $db->rollback();
  34.             // 异常code码(CODE_117006008)返回素材信息,不直接抛异常
  35.             if ($logicException->getCode() == RelebookUserCodeEnum::CODE_117006008) {
  36.                 $data = collect(json_decode($logicException->getMessage()))->toArray();
  37.                 $data['order_no'] = '';
  38.                 $data['pay_url'] = '';
  39.                 return $data;
  40.             }
  41.             throw new \LogicException($logicException->getMessage(), $logicException->getCode());
  42.         } catch (\Exception $exception) {
  43.             $db->rollback();
  44.             throw new \Exception($exception->getMessage(), intval($exception->getCode()));
  45.         }
  46.     }
复制代码
这些 行为类 都继承BehaviorOrderCreateInterface 接口
  1. interface BehaviorOrderCreateInterface
  2. {
  3.     public function handle(OrderCreateService $orderCreateService);
  4. }
复制代码
最后让我们来瞧瞧OrderCreateService类的实现。
$behaviorList 用来存放 registerBehavior方法注册的行为类
最后在create方法里面运行
  1. class OrderCreateService
  2. {
  3.     public array $params;
  4.     public array $params;
  5.     /** 素材信息 **/
  6.     public array $res;
  7.     /**订单信息 **/
  8.     public array $order_info;
  9.     /**子订单信息 **/
  10.     public array $subOrders;
  11.     /**购物车信息 **/
  12.     public array $cartItem;
  13.     /**支付服务 **/
  14.     public PaymentInterface $paymentService;
  15.     /**支付信息 **/
  16.     public array $payment;
  17.     /**
  18.      * @var array|\Closure[]
  19.      */
  20.     private array $behaviorList = [];
  21.     public function __construct($params)
  22.     {
  23.         $this->params = $params;
  24.     }
  25.     /**
  26.      * 订单创建行为注册
  27.      * @param \Closure ...$callback
  28.      * @return $this
  29.      * @date 2023/6/29 13:58
  30.      */
  31.     public function registerBehavior(\Closure ...$callback): static
  32.     {
  33.         $this->behaviorList = array_merge($this->behaviorList,$callback);
  34.         return $this;
  35.     }
  36. /**
  37.      * 创建订单
  38.      * @return mixed
  39.      * @date 2023/6/29 14:21
  40.      */
  41.     public function create()
  42.     {
  43.         $this->params['order_type'] = PayEnum::ORDER_TYPE_RES;
  44.         // A、执行订单创建行为
  45.         foreach ($this->behaviorList as $behavior) {
  46.             $behavior()->handle($this);
  47.         }
  48.         // B、订单返回数据
  49.         $this->data = array_merge($this->data, $this->payment);
  50.         $this->data['order_type'] = $this->params['order_type'];
  51.         $this->data['cash_type'] = $this->params['cash_type'];
  52.         $this->data['order_no'] = $this->order_info['order_no'];
  53.         $this->data['pay_url'] = $this->payment['pay_url'];
  54.         $this->data['type'] = RelebookUserOrder::ORDER_CREATE_STATUS_SUCCESS;
  55.         $this->data['res'] = [];
  56.         return $this->data;
  57.     }
  58. }
复制代码
Go重构的挑战与解决方案



  • 挑战 1:状态共享 (Go 没有类属性)问题: Go 没有类的概念,不同的小任务(函数/方法)不能直接共享一个“服务对象”的状态。解决方案: 引入 DTO (Data Transfer Object)。DTO 是什么? 一个专门用来在不同部分之间传递数据的结构体。它就像是一个共享的“数据盒子”。作用: 将所有小任务需要共享的数据(请求参数、中间结果、最终响应、错误信息)都定义在这个结构体 (OrderBehaviorDTO) 里。如何工作: 每个小任务接收这个 DTO 指针作为参数,从中读取需要的数据,并将自己产生的结果写回到 DTO 中。这样,后续的任务就能访问到前面任务写入的数据。
  • 挑战 2:异常处理 (Go 没有 try-catch)问题: Go 通常通过函数返回 error 值来处理错误,不能像 PHP 那样在任意地方 throw 并被外层 catch。解决方案:在 DTO 中加入专门的错误字段 (Err)。每个小任务在执行过程中,如果遇到错误,不直接 panic 或抛异常,而是将错误信息设置到 DTO 的 Err 字段 (或 Response.Code/ErrMsg) 中,然后返回 DTO。主流程 (Handle 方法) 检查每个任务执行后 DTO 中的错误状态。一旦发现错误,立即终止后续任务并返回错误响应。
Go 实现的核心逻辑 (简化说明)



  • 定义 DTO 结构体 (OrderBehaviorDTO): 包含所有需要流转的数据(输入参数、支付结果、订单信息、错误码等)。
  • 定义任务接口 (BehaviorOrderCreateInterface): 要求所有小任务都必须实现一个 Handle(*OrderBehaviorDTO) *OrderBehaviorDTO 方法。
  • 创建主服务 (MakeOrderService):初始化时创建 DTO。将需要执行的小任务 (如 OrderValidate, OrderCreate, OrderPayment, OrderBuried) 添加到任务列表 (Behavior 切片)。
  • 执行主流程 (Handle 方法):按顺序遍历任务列表,调用每个任务的 Handle 方法,并传入 同一个 DTO 指针。每个任务从 DTO 读数据,处理业务,把结果写回 DTO。检查每个任务执行后 DTO 中的错误状态,遇错即停。
  • 组装最终响应: 所有任务成功后,从 DTO 中提取所需数据,组装成最终的订单创建响应 (OrderMakeResponse)。
关键优势 (使用 DTO + 接口模式)



  • 解耦清晰: 每个小任务只依赖 DTO 接口,不直接依赖其他任务的具体实现。
  • 状态管理: DTO 作为唯一的、明确的数据交换通道,解决了 Go 中跨函数/方法状态共享的问题。
  • 灵活可扩展: 添加新任务只需实现接口并注册到列表即可。
  • 错误处理适配 Go 习惯: 通过返回值和 DTO 中的错误状态显式处理错误,符合 Go 的 error 处理哲学。

实现代码:1.定义DTO结构体内容
// OrderBehaviorDTO 用于解耦在不同结构体(服务)里面传递的数据结构体
  1. type OrderBehaviorDTO struct {
  2.         Params    *order.OrderMakeRequest // 请求参数
  3.         Response  *order.OrderMakeResponse // 响应参数
  4.         UserId    int // 用户id
  5.         Uid       string // uid
  6.         CartItem  []relebook.LlRelebookCartItem // 子服务查询产生的购物车信息
  7.         Res       map[int32]relebook.LlRelebookRes // 子服务查询产生的素材信息
  8.         Orders    OrderInfos  // 子服务创建的订单信息 -用于埋点
  9.         PayResult payment.PayResult // 支付子服务产生的支付信息 -用于埋点
  10.     Err Errer
  11. }
复制代码
// 响应参数的proto
  1. message OrderMakeResponse {
  2.   string order_no = 1;
  3.   string pay_url = 2;
  4.   string payment_intent_id = 3;
  5.   string client_secret = 4;
  6.   int32 order_type = 5;
  7.   int32 cash_type = 6;
  8.   int32 type = 7;
  9.   repeated Empty res = 8;
  10.   // 异常码和返回消息
  11.   int32 code = 9;
  12.   string err_msg = 10;
  13. }
复制代码

  • 将订单限流功能抽离成限流组件
  • 执行 orderService.NewMakeOrder方法 实例化订单创建服务并注入DTO结构体然后调用.Handle方法
  1. func (*OrderLogic) OrderMake(ctx context.Context, in *order.OrderMakeRequest) (*order.OrderMakeResponse, error) {
  2.     // 订单请求限制
  3.     if utils.SlidingWindowRateLimiter(utils.GetLimitKey(ctx, "make_order"), 1, time.Second*1) != nil {
  4.         result := &order.OrderMakeResponse{}
  5.         result.Code = code.CODE_117001011
  6.         result.ErrMsg = lang.OperatingLimit
  7.         return result, nil
  8.     }
  9.     // 开始执行业务
  10.     return orderService.NewMakeOrder(&dto.OrderBehaviorDTO{
  11.         Params:   in,
  12.         Response: &order.OrderMakeResponse{},
  13.         UserId:   cast.ToInt(trace.GetUserId(ctx)),
  14.         Uid:      in.Uid,
  15.     }).Handle()
  16. }
复制代码
MakeOrderService结构体内容
定义了支付、素材信息、素材封装数据DAO、用户id、以及DTO 和行为单元
最后执行 Handle方法进行行为服务执行。
  1. type MakeOrderService struct {
  2.         payService payment.PaymentInterface // 支付服务接口
  3.         resInfos   map[int32]relebook.LlRelebookRes // 订单支付的素材
  4.         resDAOImpl dao.ResDAOImpl // 素材表DAO封装的查询集合
  5.         userId     int // 用户id
  6.         Dto        *dto.OrderBehaviorDTO // DTO
  7.         Behavior   []BehaviorOrderCreateInterface // 行为单元
  8. }
  9. type BehaviorOrderCreateInterface interface {
  10.         Handle(Dto *dto.OrderBehaviorDTO) *dto.OrderBehaviorDTO
  11. }
  12. func (m *MakeOrderService) SetBehavior(behavior BehaviorOrderCreateInterface) *MakeOrderService {
  13.         m.Behavior = append(m.Behavior, behavior)
  14.         return m
  15. }
  16. func NewMakeOrder(Dto *dto.OrderBehaviorDTO) *MakeOrderService {
  17.         service, _ := payment.GetPaymentService(int(Dto.Params.CashType))
  18.         return &MakeOrderService{
  19.                 payService: service,
  20.                 Dto:        Dto,
  21.         }
  22. }
  23. func (m *MakeOrderService) Handle() (*order.OrderMakeResponse, error) {
  24.         // 支付验签
  25.         if err := m.payService.Validate(payment.PaymentParams{}); err != nil {
  26.                 m.Dto.Response.Code = code.CODE_117001001
  27.                 m.Dto.Response.ErrMsg = err.Error()
  28.                 return m.Dto.Response, nil
  29.         }
  30.         //设置需要写入的服务
  31.         m.SetBehavior(new(behavior_create.OrderValidate)).
  32.                 SetBehavior(new(behavior_create.OrderCreate)).
  33.                 SetBehavior(new(behavior_create.OrderPayment)).
  34.                 SetBehavior(new(behavior_create.OrderBuried))
  35.         // 执行服务
  36.         for _, behavior := range m.Behavior {
  37.                 if behavior.Handle(m.Dto).Response.Code != 0 {
  38.                         return m.Dto.Response, nil
  39.                 }
  40.         }
  41.         // 执行完成后
  42.         m.Dto.Response.ClientSecret = m.Dto.PayResult.ClientSecret
  43.         m.Dto.Response.PayUrl = m.Dto.PayResult.PaymentIntentId
  44.         m.Dto.Response.PayUrl = m.Dto.PayResult.PayUrl
  45.         m.Dto.Response.OrderType = m.Dto.Params.OrderType
  46.         m.Dto.Response.CashType = m.Dto.Params.CashType
  47.         m.Dto.Response.OrderNo = m.Dto.Orders.OrderInfo.OrderNo
  48.         m.Dto.Response.Type = relebook.ORDER_CREATE_STATUS_SUCCESS
  49.         return m.Dto.Response, nil
  50. }
复制代码
来源:豆瓜网用户自行投稿发布,如果侵权,请联系站长删除

相关推荐

您需要登录后才可以回帖 登录 | 立即注册