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() }