關於處理電商系統訂單狀態的流轉,分享下我的技術方案(附帶原始碼)

新亮筆記發表於2021-07-05

前言

在設計電商系統訂單模組時,訂單會涉及各種狀態以及狀態與狀態之間的流轉,可擴充套件性可維護性 是我們需要關注的重點!本文分享一下我的技術方案。

如上圖,使用 golang 實現上圖的訂單流轉,同時當後續增加訂單狀態或訂單事件時,可以進行快速完成。

目的

關於訂單狀態的處理,使用統一入口,提高程式的 可擴充套件性可維護性

邏輯分析

訂單狀態包括:預設已預訂已確認已鎖定

訂單事件包括:建立訂單確認訂單修改訂單支付訂單

通過上圖我們還知道了狀態與事件之間的關係,比如只有 已確認 的訂單才可以進行 修改訂單

需要考慮如下問題:

  1. 當訂單狀態增加時,如何儘可能少的改動或改動對歷史影響不大?
  2. 如果在同一入口呼叫,每個事件的處理方法需要的入參都有所不同,如何處理?
  3. 當某個事件完成後,有可能會進行發簡訊或客戶端 Push 的操作,如何處理?
  4. 有可能某個事件,在不同平臺(C端、商家後臺、管理平臺)的處理邏輯也有些不同,如何處理?

如何設計程式碼能夠解決以上問題?

下面是我的一種程式碼實現,供大家參考,實現了在 建立訂單 時,進行傳入引數和完成後給使用者傳送簡訊,其他事件的操作,同理就可以實現。

程式碼實現

定義狀態

// 定義訂單狀態
const (
	StatusDefault   = State(0)
	StatusReserved  = State(10)
	StatusConfirmed = State(20)
	StatusLocked    = State(30)
)

// statusText 定義訂單狀態文案
var statusText = map[State]string{
	StatusDefault:   "預設",
	StatusReserved:  "已預訂",
	StatusConfirmed: "已確認",
	StatusLocked:    "已鎖定",
}

// statusEvent 定義訂單狀態對應的可操作事件
var statusEvent = map[State][]Event{
	StatusDefault:   {EventCreate},
	StatusReserved:  {EventConfirm},
	StatusConfirmed: {EventModify, EventPay},
}

func StatusText(status State) string {
	return statusText[status]
}

當有新訂單狀態的增加時,在此檔案中增加相應狀態即可,同時維護好訂單狀態與訂單事件之間的關係。

定義事件

// 定義訂單事件
const (
	EventCreate  = Event("建立訂單")
	EventConfirm = Event("確定訂單")
	EventModify  = Event("修改訂單")
	EventPay     = Event("支付訂單")
)

// 定義訂單事件對應的處理方法
var eventHandler = map[Event]Handler{
	EventCreate:  handlerCreate,
	EventConfirm: handlerConfirm,
	EventModify:  handlerModify,
	EventPay:     handlerPay,
}

當有新訂單事件的增加時,在此檔案中增加相應事件即可,同時維護好訂單事件與事件實現方法之間的關係。

定義事件的處理方法

var (
	// handlerCreate 建立訂單
	handlerCreate = Handler(func(opt *Opt) (State, error) {
		message := fmt.Sprintf("正在處理建立訂單邏輯,訂單ID(%d), 訂單名稱(%s) ... 處理完畢!", opt.OrderId, opt.OrderName)
		fmt.Println(message)

		if opt.HandlerSendSMS != nil {
			_ = opt.HandlerSendSMS("18888888888", "恭喜你預定成功了!")
		}

		return StatusReserved, nil
	})

	// handlerConfirm 確認訂單
	handlerConfirm = Handler(func(opt *Opt) (State, error) {
		return StatusConfirmed, nil
	})

	// handlerModify 修改訂單
	handlerModify = Handler(func(opt *Opt) (State, error) {
		return StatusReserved, nil
	})

	// handlerPay 支付訂單
	handlerPay = Handler(func(opt *Opt) (State, error) {
		return StatusLocked, nil
	})
)

在此檔案中維護具體的事件處理方法,如果邏輯比較複雜可以考慮拆分檔案處理。

核心程式碼

type State int                             // 狀態
type Event string                          // 事件
type Handler func(opt *Opt) (State, error) // 處理方法,並返回新的狀態

// FSM 有限狀態機
type FSM struct {
	mu       sync.Mutex                  // 排他鎖
	state    State                       // 當前狀態
	handlers map[State]map[Event]Handler // 當前狀態可觸發的有限個事件
}

// 獲取當前狀態
func (f *FSM) getState() State {
	return f.state
}

// 設定當前狀態
func (f *FSM) setState(newState State) {
	f.state = newState
}

// addHandlers 新增事件和處理方法
func (f *FSM) addHandlers() (*FSM, error) {
	...

	return f, nil
}

// Call 事件處理
func (f *FSM) Call(event Event, opts ...Option) (State, error) {
	f.mu.Lock()
	defer f.mu.Unlock()

	...

	return f.getState(), nil
}

// NewFSM 例項化 FSM
func NewFSM(initState State) (fsm *FSM, err error) {
	fsm = new(FSM)
	fsm.state = initState
	fsm.handlers = make(map[State]map[Event]Handler)

	fsm, err = fsm.addHandlers()
	if err != nil {
		return
	}

	return
}

對訂單狀態的操作,只需要使用 Call 方法即可!

關於方法 addHandlersCall 的程式碼就不貼了,在文章後面我提供了原始碼地址,供大家下載。

呼叫方式

例如當前狀態為 預設狀態,依次進行如下操作:

  • 建立訂單,狀態變為 已預訂
  • 修改訂單,不可操作(已預訂狀態不可修改);
  • 確定訂單,狀態變為 已確認
  • 修改訂單,狀態變為 已預訂
  • 確定訂單,狀態變為 已確認
  • 支付訂單,狀態變為 已鎖定
// 通過訂單ID 或 其他資訊查詢到訂單狀態
orderStatus := order.StatusDefault

orderMachine, err := order.NewFSM(orderStatus)
if err != nil {
	fmt.Println(err.Error())
	return
}

// 建立訂單,訂單建立成功後再給使用者傳送簡訊
if _, err = orderMachine.Call(order.EventCreate,
	order.WithOrderId(1),
	order.WithOrderName("測試訂單"),
	order.WithHandlerSendSMS(sendSMS),
); err != nil {
	fmt.Println(err.Error())
}

// 修改訂單
if _, err = orderMachine.Call(order.EventModify); err != nil {
	fmt.Println(err.Error())
}

// 確認訂單
if _, err = orderMachine.Call(order.EventConfirm); err != nil {
	fmt.Println(err.Error())
}

// 修改訂單
if _, err = orderMachine.Call(order.EventModify); err != nil {
	fmt.Println(err.Error())
}

// 確認訂單
if _, err = orderMachine.Call(order.EventConfirm); err != nil {
	fmt.Println(err.Error())
}

// 支付訂單
if _, err = orderMachine.Call(order.EventPay); err != nil {
	fmt.Println(err.Error())
}

輸出:

正在處理建立訂單邏輯,訂單ID(1), 訂單名稱(測試訂單) ... 處理完畢!
傳送簡訊,給(18888888888)傳送了(恭喜你預定成功了!)
操作[建立訂單],狀態從 [預設] 變成 [已預訂]
[警告] 狀態(已預訂)不允許操作(修改訂單)
操作[確定訂單],狀態從 [已預訂] 變成 [已確認]
操作[修改訂單],狀態從 [已確認] 變成 [已預訂]
操作[確定訂單],狀態從 [已預訂] 變成 [已確認]
操作[支付訂單],狀態從 [已確認] 變成 [已鎖定]

小結

以上就是我的技術方案,希望能對你有所幫助,感興趣的可以再進行封裝,上述程式碼已提交到 github go-fsm-order,供下載使用。

相關文章