Table of Contents
前言
2023 鐵人賽 DAY-24 交易功能
昨天把 Golang
呼叫 Python
交易的 gRPC
這段串通了
雖然在取消下單這邊有一點不完美
但理念大致是對的
今天要來把這些交易功能
在 Golang
上實作成 RESTful API
Usecase
我們在前面的天數
把各種功能包裝再包裝
最終還是要拿來使用
在 Clean Code
的概念裡
除了每一個被包裝的功能最好都是能夠被清楚表達意圖之外
最後的每一個商業邏輯
都是透過一個又一個的包裝功能組合
而不要在過度的加工
甚至可以說,最終的商業邏輯是零加工
僅是像堆積木一樣
把功能實作出來
對這次的專案來說Flutter
是介面的部分Python + Golang
是後端
很明顯是 Flutter
去使用後端的 Usecase
在實作 RESTful API
之前
我先把一些必須要的 Usecase
示範出來
可以依照需求再增加
從 entity 開始
這邊以期貨為例
type FutureOrder struct {
BaseOrder `json:"base_order"`
Code string `json:"code"`
Future *Future `json:"future"`
}
type BaseOrder struct {
OrderID string `json:"order_id"`
Status OrderStatus `json:"status"`
OrderTime time.Time `json:"order_time"`
Action OrderAction `json:"action"`
Price float64 `json:"price"`
Quantity int64 `json:"quantity"`
}
const (
// StatusUnknow -.
StatusUnknow OrderStatus = iota
// StatusPendingSubmit -.
StatusPendingSubmit
// StatusPreSubmitted -.
StatusPreSubmitted
// StatusSubmitted -.
StatusSubmitted
// StatusFailed -.
StatusFailed
// StatusCancelled -.
StatusCancelled
// StatusFilled -.
StatusFilled
// StatusPartFilled -.
StatusPartFilled
)
type OrderStatus int64
func (s OrderStatus) String() string {
switch s {
case StatusUnknow:
return "Unknow"
case StatusPendingSubmit:
return "PendingSubmit"
case StatusPreSubmitted:
return "PreSubmitted"
case StatusSubmitted:
return "Submitted"
case StatusFailed:
return "Failed"
case StatusCancelled:
return "Cancelled"
case StatusFilled:
return "Filled"
case StatusPartFilled:
return "PartFilled"
default:
return ""
}
}
const (
// ActionNone -.
ActionNone OrderAction = iota
// ActionBuy -.
ActionBuy
// ActionSell -.
ActionSell
)
type OrderAction int64
func (a OrderAction) String() string {
switch a {
case ActionNone:
return ActionStringNone
case ActionBuy:
return ActionStringBuy
case ActionSell:
return ActionStringSell
default:
return ""
}
}
type Future struct {
Code string `json:"code"`
Symbol string `json:"symbol"`
Name string `json:"name"`
Category string `json:"category"`
DeliveryMonth string `json:"delivery_month"`
DeliveryDate time.Time `json:"delivery_date"`
UnderlyingKind string `json:"underlying_kind"`
Unit int64 `json:"unit"`
LimitUp float64 `json:"limit_up"`
LimitDown float64 `json:"limit_down"`
Reference float64 `json:"reference"`
UpdateDate time.Time `json:"update_date"`
}
這邊就偷懶不講解太細節的東西
基本上就是一張單,該有的欄位
股票或者期貨都是差不多
這次的鐵人賽會比較多篇幅在期貨
其中一個原因就是寫稿的時間,大部分股票都已經收盤🤣
Interface
type Trade interface {
BuyFuture(order *entity.FutureOrder) (string, entity.OrderStatus, error)
SellFuture(order *entity.FutureOrder) (string, entity.OrderStatus, error)
CancelFutureOrderByID(orderID string) (string, entity.OrderStatus, error)
}
這邊定義了三個方法
是交易期貨最基本的三個方法『買』『賣』『取消』
先來定義一下我們需要的 struct
type TradeUseCase struct {
trade pb.TradeInterfaceClient
logger *log.Log
}
首先,至少需要一個跟 Python
那邊的 gRPC
接口
然後就可以很快完成下面的雛形
func NewTrade() Trade {
logger := log.Get()
conn, err := grpc.Dial(address, grpc.WithInsecure())
if err != nil {
logger.Fatalf("did not connect: %v", err)
}
trade := pb.NewTradeInterfaceClient(conn)
return &TradeUseCase{
trade: trade,
logger: logger,
}
}
func (t *TradeUseCase) BuyFuture(order *entity.FutureOrder) (string, entity.OrderStatus, error) {
t.logger.Warn("BuyFuture...")
return "", entity.StatusUnknow, nil
}
func (t *TradeUseCase) SellFuture(order *entity.FutureOrder) (string, entity.OrderStatus, error) {
t.logger.Warn("SellFuture...")
return "", entity.StatusUnknow, nil
}
func (t *TradeUseCase) CancelFutureOrderByID(orderID string) (string, entity.OrderStatus, error) {
t.logger.Warn("CancelFutureOrderByID...")
return "", entity.StatusUnknow, nil
}
這邊只是雛型
先讓我們快速的往下寫
Gin@Golang
要建立 RESTful API
會繼續沿用之前的 Gin 框架
剛剛完成了交易相關的 Usecase
在 API
這層,就是簡單地拿來使用
先來開一個 Route
tradeUsecase := trade.NewTrade()
tradeRoute := handler.Group("/trade")
tradeRoute.PUT("/future/buy", func(c *gin.Context) {
tradeUsecase.BuyFuture(nil)
c.JSON(http.StatusOK, nil)
})
tradeRoute.PUT("/future/sell", func(c *gin.Context) {
tradeUsecase.SellFuture(nil)
c.JSON(http.StatusOK, nil)
})
tradeRoute.PUT("/future/cancel", func(c *gin.Context) {
type cancelOrder struct {
OrderID string `json:"order_id"`
}
var order cancelOrder
if err := c.ShouldBindJSON(&order); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
tradeUsecase.CancelFutureOrderByID(order.OrderID)
c.JSON(http.StatusOK, nil)
})
先簡單的確認 API
是否有成功開啟,並能夠被呼叫
如果在 debug mode
下 gin
應該要顯示出以下路由
[GIN-debug] PUT /trade/future/buy --> main.main.func2 (2 handlers)
[GIN-debug] PUT /trade/future/sell --> main.main.func3 (2 handlers)
[GIN-debug] PUT /trade/future/cancel --> main.main.func4 (2 handlers)
Postman
用 Postman 來試試看
這時候回來看一下 Golang
印了什麼
WARN[2023-10-09 10:19:40] BuyFuture...
WARN[2023-10-09 10:20:31] SellFuture...
WARN[2023-10-09 10:21:22] CancelFutureOrderByID...TEST_ID
總結
2023 鐵人賽 DAY-24 交易功能
很順利的把 RESTful API
簡單的開出來
但別忘記,我們在 Usecase
裡面還沒有把真正的交易放進去
明天將會是後端的完成日
放進去,就可以來好好地刻 Flutter
介面