前言
在設計電商系統訂單模組時,訂單會涉及各種狀態以及狀態與狀態之間的流轉,
可擴充套件性
、可維護性
是我們需要關注的重點!本文分享一下我的技術方案。
如上圖,使用 golang
實現上圖的訂單流轉,同時當後續增加訂單狀態或訂單事件時,可以進行快速完成。
目的
關於訂單狀態的處理,使用統一入口,提高程式的 可擴充套件性
和 可維護性
。
邏輯分析
訂單狀態包括:預設
、已預訂
、已確認
、已鎖定
。
訂單事件包括:建立訂單
、確認訂單
、修改訂單
、支付訂單
。
通過上圖我們還知道了狀態與事件之間的關係,比如只有 已確認
的訂單才可以進行 修改訂單
。
需要考慮如下問題:
- 當訂單狀態增加時,如何儘可能少的改動或改動對歷史影響不大?
- 如果在同一入口呼叫,每個事件的處理方法需要的入參都有所不同,如何處理?
- 當某個事件完成後,有可能會進行發簡訊或客戶端 Push 的操作,如何處理?
- 有可能某個事件,在不同平臺(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
方法即可!
關於方法 addHandlers
和 Call
的程式碼就不貼了,在文章後面我提供了原始碼地址,供大家下載。
呼叫方式
例如當前狀態為 預設狀態
,依次進行如下操作:
建立訂單
,狀態變為已預訂
;修改訂單
,不可操作(已預訂狀態不可修改);確定訂單
,狀態變為已確認
;修改訂單
,狀態變為已預訂
;確定訂單
,狀態變為已確認
;支付訂單
,狀態變為已鎖定
;
// 通過訂單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,供下載使用。