source code:
活動訂單需由訂單模組管理,可以用hash table以orderID: order
儲存所有活動訂單,在需要以用戶ID取得相關活動訂單時,可用兩層hash table以userID: orderID: order
取得訂單。
由於在撮合時也有機會透過API讀取memory的活動訂單,所以須用read-write-lock保護。
type orderUseCase struct {
lock *sync.RWMutex
activeOrders map[int]*domain.OrderEntity
userOrdersMap map[int]map[int]*domain.OrderEntity
// another code...
}
活動訂單的欄位介紹如下:
type OrderEntity struct {
ID int // 訂單ID
SequenceID int // 訂單由定序模組所定的ID
UserID int // 用戶ID
Price decimal.Decimal // 價格
Direction DirectionEnum // 買單還是賣單
// 狀態:
// 完全成交(Fully Filled)、
// 部分成交(Partial Filled)、
// 等待成交(Pending)、
// 完全取消(Fully Canceled)、
// 部分取消(Partial Canceled)
Status OrderStatusEnum
Quantity decimal.Decimal // 數量
UnfilledQuantity decimal.Decimal // 未成交數量
CreatedAt time.Time // 創建時間
UpdatedAt time.Time // 更新時間
}
訂單模組主要的use case有創建訂單CreateOrder()
、刪除訂單RemoveOrder()
、取得訂單GetUserOrders()
,由於在創建時需要凍結資產、刪除時需要解凍資產,所以須注入資產模組UserAssetUseCase
。
type OrderUseCase interface {
CreateOrder(ctx context.Context, sequenceID int, orderID, userID int, direction DirectionEnum, price, quantity decimal.Decimal, ts time.Time) (*OrderEntity, *TransferResult, error)
RemoveOrder(ctx context.Context, orderID int) error
GetUserOrders(userID int) (map[int]*OrderEntity, error)
// another code...
}
資產模組在此處的命名為o.assetUseCase
。
在創建訂單時呼叫CreateOrder()
,需先判斷買賣單凍結用戶資產o.assetUseCase.Freeze()
,之後創建order
存入o.activeOrders
與o.userOrdersMap
,由於過程中有資產的變化,所以除了order
以外,也需將transferResult
回應給下游服務。
func (o *orderUseCase) CreateOrder(ctx context.Context, sequenceID int, orderID int, userID int, direction domain.DirectionEnum, price decimal.Decimal, quantity decimal.Decimal, ts time.Time) (*domain.OrderEntity, *domain.TransferResult, error) {
var err error
transferResult := new(domain.TransferResult)
switch direction {
case domain.DirectionSell:
transferResult, err = o.assetUseCase.Freeze(ctx, userID, o.baseCurrencyID, quantity)
if err != nil {
return nil, nil, errors.Wrap(err, "freeze base currency failed")
}
case domain.DirectionBuy:
transferResult, err = o.assetUseCase.Freeze(ctx, userID, o.quoteCurrencyID, price.Mul(quantity))
if err != nil {
return nil, nil, errors.Wrap(err, "freeze base currency failed")
}
default:
return nil, nil, errors.New("unknown direction")
}
order := domain.OrderEntity{
ID: orderID,
SequenceID: sequenceID,
UserID: userID,
Direction: direction,
Price: price,
Quantity: quantity,
UnfilledQuantity: quantity,
Status: domain.OrderStatusPending,
CreatedAt: ts,
UpdatedAt: ts,
}
o.lock.Lock()
o.activeOrders[orderID] = &order
if _, ok := o.userOrdersMap[userID]; !ok {
o.userOrdersMap[userID] = make(map[int]*domain.OrderEntity)
}
o.userOrdersMap[userID][orderID] = &order
o.lock.Unlock()
return &order, transferResult, nil
}
在撮合訂單完全成交、取消訂單時會呼叫RemoveOrder()
,o.activeOrders
刪除訂單,而o.userOrdersMap
透過訂單的userID查找也刪除訂單。
func (o *orderUseCase) RemoveOrder(ctx context.Context, orderID int) error {
o.lock.Lock()
defer o.lock.Unlock()
removedOrder, ok := o.activeOrders[orderID]
if !ok {
return errors.New("order not found in active orders")
}
delete(o.activeOrders, orderID)
userOrders, ok := o.userOrdersMap[removedOrder.UserID]
if !ok {
return errors.New("user orders not found")
}
_, ok = userOrders[orderID]
if !ok {
return errors.New("order not found in user orders")
}
delete(userOrders, orderID)
return nil
}
API取得訂單會呼叫GetUserOrders()
,為了避免race-condition,查找到訂單後需將訂單clone再回傳。
func (o *orderUseCase) GetUserOrders(userId int) (map[int]*domain.OrderEntity, error) {
o.lock.RLock()
defer o.lock.RUnlock()
userOrders, ok := o.userOrdersMap[userId]
if !ok {
return nil, errors.New("get user orders failed")
}
userOrdersClone := make(map[int]*domain.OrderEntity, len(userOrders))
for orderID, order := range userOrders {
userOrdersClone[orderID] = order.Clone()
}
return userOrdersClone, nil
}