153 lines
4.7 KiB
Go
153 lines
4.7 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/genex/trading-service/internal/domain/entity"
|
|
"github.com/genex/trading-service/internal/domain/event"
|
|
"github.com/genex/trading-service/internal/domain/repository"
|
|
"github.com/genex/trading-service/internal/domain/vo"
|
|
)
|
|
|
|
// TradeService is the application-level use-case service for trading operations.
|
|
// It coordinates between the matching engine, repositories, and event publishing.
|
|
type TradeService struct {
|
|
orderRepo repository.OrderRepository
|
|
tradeRepo repository.TradeRepository
|
|
matchingService *MatchingService
|
|
publisher event.EventPublisher
|
|
}
|
|
|
|
// NewTradeService creates a new TradeService with all dependencies injected.
|
|
func NewTradeService(
|
|
orderRepo repository.OrderRepository,
|
|
tradeRepo repository.TradeRepository,
|
|
matchingService *MatchingService,
|
|
publisher event.EventPublisher,
|
|
) *TradeService {
|
|
if publisher == nil {
|
|
publisher = &event.NoopEventPublisher{}
|
|
}
|
|
return &TradeService{
|
|
orderRepo: orderRepo,
|
|
tradeRepo: tradeRepo,
|
|
matchingService: matchingService,
|
|
publisher: publisher,
|
|
}
|
|
}
|
|
|
|
// PlaceOrderInput carries the validated input for placing an order.
|
|
type PlaceOrderInput struct {
|
|
UserID string
|
|
CouponID string
|
|
Side vo.OrderSide
|
|
Type vo.OrderType
|
|
Price vo.Price
|
|
Quantity vo.Quantity
|
|
}
|
|
|
|
// PlaceOrderOutput is the result of a place order use case.
|
|
type PlaceOrderOutput struct {
|
|
Order *entity.Order
|
|
Trades []*entity.Trade
|
|
}
|
|
|
|
// PlaceOrder handles the full workflow of placing an order:
|
|
// 1. Create the domain entity
|
|
// 2. Persist the order
|
|
// 3. Submit to matching engine
|
|
// 4. Persist resulting trades
|
|
// 5. Update order status
|
|
func (s *TradeService) PlaceOrder(ctx context.Context, input PlaceOrderInput) (*PlaceOrderOutput, error) {
|
|
// Generate order ID
|
|
orderID := fmt.Sprintf("ord-%d", time.Now().UnixNano())
|
|
|
|
// Create domain entity with validation
|
|
order, err := entity.NewOrder(
|
|
orderID, input.UserID, input.CouponID,
|
|
input.Side, input.Type, input.Price, input.Quantity,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid order: %w", err)
|
|
}
|
|
|
|
// Persist the new order
|
|
if err := s.orderRepo.Save(ctx, order); err != nil {
|
|
return nil, fmt.Errorf("failed to save order: %w", err)
|
|
}
|
|
|
|
// Submit to matching engine
|
|
matchResult := s.matchingService.PlaceOrder(order)
|
|
|
|
// Persist resulting trades
|
|
for _, trade := range matchResult.Trades {
|
|
if err := s.tradeRepo.Save(ctx, trade); err != nil {
|
|
return nil, fmt.Errorf("failed to save trade %s: %w", trade.ID, err)
|
|
}
|
|
}
|
|
|
|
// Update order status after matching
|
|
if err := s.orderRepo.UpdateStatus(ctx, order); err != nil {
|
|
return nil, fmt.Errorf("failed to update order status: %w", err)
|
|
}
|
|
|
|
return &PlaceOrderOutput{
|
|
Order: matchResult.UpdatedOrder,
|
|
Trades: matchResult.Trades,
|
|
}, nil
|
|
}
|
|
|
|
// CancelOrder cancels an active order by removing it from the order book.
|
|
func (s *TradeService) CancelOrder(ctx context.Context, couponID, orderID string, side vo.OrderSide) error {
|
|
success := s.matchingService.CancelOrder(couponID, orderID, side)
|
|
if !success {
|
|
return fmt.Errorf("order not found in order book: %s", orderID)
|
|
}
|
|
|
|
// Update order status in repository
|
|
order, err := s.orderRepo.FindByID(ctx, orderID)
|
|
if err == nil && order != nil {
|
|
order.Cancel()
|
|
_ = s.orderRepo.UpdateStatus(ctx, order)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetOrdersByUser retrieves all orders for a given user.
|
|
func (s *TradeService) GetOrdersByUser(ctx context.Context, userID string) ([]*entity.Order, error) {
|
|
return s.orderRepo.FindByUserID(ctx, userID)
|
|
}
|
|
|
|
// GetOrder retrieves a single order by ID.
|
|
func (s *TradeService) GetOrder(ctx context.Context, orderID string) (*entity.Order, error) {
|
|
return s.orderRepo.FindByID(ctx, orderID)
|
|
}
|
|
|
|
// GetTradesByOrder retrieves all trades associated with an order.
|
|
func (s *TradeService) GetTradesByOrder(ctx context.Context, orderID string) ([]*entity.Trade, error) {
|
|
return s.tradeRepo.FindByOrderID(ctx, orderID)
|
|
}
|
|
|
|
// GetRecentTrades retrieves the most recent trades.
|
|
func (s *TradeService) GetRecentTrades(ctx context.Context, limit int) ([]*entity.Trade, error) {
|
|
return s.tradeRepo.FindRecent(ctx, limit)
|
|
}
|
|
|
|
// GetOrderBookSnapshot delegates to the matching service for order book data.
|
|
func (s *TradeService) GetOrderBookSnapshot(couponID string, depth int) (bids []entity.PriceLevel, asks []entity.PriceLevel) {
|
|
return s.matchingService.GetOrderBookSnapshot(couponID, depth)
|
|
}
|
|
|
|
// GetAllOrderBooks returns all active order books (admin use).
|
|
func (s *TradeService) GetAllOrderBooks() map[string]*entity.OrderBook {
|
|
return s.matchingService.GetAllOrderBooks()
|
|
}
|
|
|
|
// GetTradeCount returns the total trade count.
|
|
func (s *TradeService) GetTradeCount() int64 {
|
|
return s.matchingService.GetTradeCount()
|
|
}
|