Исправьте ошибки в этом обработчике бронирования HandleBookingOrder.
Этот HandleBookingOrder блокирует пользователя, бронирует рейс в цикле с повтором и возвращает чек. Он не компилируется и теряет блокировку пользователя на путях ошибок.
type BookingServiceError struct {
error
TryAgain bool
}
func (s *OrderService) HandleBookingOrder(user User) *Receipt {
receipt := Receipt{ID: uuid.New().String()}
if err := s.UserService.LockUser(user); err != nil {
log.Logger.Err(err)
return nil
}
// if err := s.UserService.UnlockUser(user); err != nil {
// return nil
// }
for {
bookingCode, err := s.BookingService.BookFlight()
if err != nil {
if err.TryAgain {
continue
}
log.Logger.Err(err)
return nil, err
}
receipt.BookedAt = time.Now().Format(time.RFC3339)
receipt.BookingCode = bookingCode
break
}
return &receipt
}Найдите и исправьте ошибки.
Смените сигнатуру на (*Receipt, error) и оборачивайте каждый сбой через %w. Вынесите UnlockUser в defer, который захватывает и оборачивает свою ошибку через именованный возврат. Дайте BookingServiceError метод Error() string, а в цикле через errors.As извлекайте его и проверяйте TryAgain, а не голое поле на interface-ошибке.
- ✗Читать
err.TryAgainнапрямую — в циклеerrхранится как interfaceerror, и поле конкретного типа недоступно безerrors.As - ✗Вызывать
UnlockUserнапрямую вместоdefer, из-за чего раннийreturnпри сбое бронирования теряет блокировку пользователя - ✗Возвращать
nilна пути сбоя блокировки в функции(*Receipt, error), и вызывающий не видит ни чека, ни ошибки
- →Почему отложенный unlock должен использовать именованный возвращаемый параметр, чтобы всплыть со своей ошибкой?
- →Как добавить ограниченное число повторов и backoff в цикл
TryAgain?
Найдите и исправьте ошибки
type BookingServiceError struct {
error
TryAgain bool
}
// нужно реализовать Error() string для BookingServiceError
func (s *OrderService) HandleBookingOrder(user User) *Receipt { // сигнатура?
receipt := Receipt{ID: uuid.New().String()}
if err := s.UserService.LockUser(user); err != nil {
log.Logger.Err(err)
return nil // ошибка не возвращается
}
// разблокировка должна уйти в defer и обернуться контекстом
// if err := s.UserService.UnlockUser(user); err != nil {
// return nil
// }
for {
bookingCode, err := s.BookingService.BookFlight()
if err != nil {
if err.TryAgain { // поле читается напрямую — нужен errors.As/Is
continue
}
log.Logger.Err(err)
return nil, err // тип возврата не совпадает с сигнатурой
}
receipt.BookedAt = time.Now().Format(time.RFC3339)
receipt.BookingCode = bookingCode
break
}
return &receipt
}Четыре дефекта: (1) сигнатура должна быть (*Receipt, error), а пути с return nil — возвращать обёрнутую через %w ошибку; (2) UnlockUser должен уйти в defer с именованным возвратом, чтобы его ошибка тоже всплыла; (3) BookingServiceError нужен метод Error() string, чтобы он был валидной error; (4) err.TryAgain недоступно на error-интерфейсе — извлеките тип через errors.As, затем читайте TryAgain.
func (s *OrderService) HandleBookingOrder(user User) (_ *Receipt, err error) {
receipt := Receipt{ID: uuid.New().String()}
if e := s.UserService.LockUser(user); e != nil {
return nil, fmt.Errorf("lock user: %w", e)
}
defer func() {
if e := s.UserService.UnlockUser(user); e != nil && err == nil {
err = fmt.Errorf("unlock user: %w", e)
}
}()
for {
bookingCode, e := s.BookingService.BookFlight()
if e != nil {
var bookErr *BookingServiceError
if errors.As(e, &bookErr) && bookErr.TryAgain {
continue
}
return nil, fmt.Errorf("book flight: %w", e)
}
receipt.BookedAt = time.Now().Format(time.RFC3339)
receipt.BookingCode = bookingCode
break
}
return &receipt, nil
}