Comments 6
В целом неплохо. Но для обработки ошибок в http есть 7807, можно реализовать его структуру.
А ещё, было бы прикольно сделать ошибки доменные с доменными кодами и сообщениями и в мидлвари коды домена мапить на коды http или другого интерфейса (grpc, etc)
Неплохой вариант, за статью лайк. В целом давно такое использую у себя.
От себя добавлю:
Ответ для internal error я бы вынес в константу или в байт массиве и его бы отдавал наружу. Там сработает оптимизатор при сборке. (На сколько я помню).
Для типовых ошибок лучше завести отдельные методы, в которых уже будет занесено и коды и сообщения для пользователя. Это позволит в структуре хранить и обрабатывать коды не только для http и при этом имея отдельные методы для ошибок это снизит количество случаев где надо помнить какие коды для какой ошибки, упростит написание документации, больше переисполтзования и тд. Все ошибки могут быть реализованы в отдельном пакете и прикрутить авто-документирование. Это крайне удобно по итогу (особенно когда 100500 ошибок в большом проекте и нужно вести их в доке). Ну и для кастомных случаев остается публичный метод с передачей параметров в функцию. Либо сделать структуру приватной и сделать обращение к ней более строгое. В общем вариантов как улучшить всегда есть.
И да, я тоже в свое время ломал голову как адекватно прикрутить проброс ошибок.
Обработка ошибки в враппере - интуитивно понятный подход, я тоже так делаю, хоть и через структуру в контексте - кроме 200 бывает 204, и обрабатываю случай, когда закомментил логику в обработчике. Тогда выбрасываю 418.
А вот добавление ошибки к сигнатуре `func (w,r)` - тупиковый путь, ИМХО. Хотя бы потому, что сильно ломается совместимость, и появляются новые костыли. И мне очень нравится оригинальная сигнатура без ошибки - она СИЛЬНО отличает код http-обработчика от обычного модуля. Подробнее писал здесь: https://habr.com/ru/articles/901938/
Вкратце: обработчику доступны разные http-коды, бизнес-логика знает только error. И выходит, что какой-то слой должен отделять мух от котлет - мне зашло оставить в обработчике, теперь "умный обработчик" знает часть логики (статья об этом).
Были возражения типа "этот слой не для этого". Здесь наверно надо определить термины. Выделяю http-слой, который реально транспорт и легко заменяется шиной данных. А есть http-слой, который обслуживает web-клиент со своей логикой и ресурсами, и который никогда не будет принимать данные из других протоколов. Это две большие разницы!
Не совсем по теме, но:
func (h *handler) signUp(w http.ResponseWriter, r *http.Request) error {
...
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
...
}
func (h *handler) signUp(c *gin.Context) error {
...
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
...
}
Я ведь правильно понимаю что во втором случае тоже контекст запроса должен в сервисный слой передаваться?
ctx, cancel := context.WithTimeout(c.Request.Context(), 10*time.Second)
Такая тема с middleware будет очень полезной для сложных эндпоинтов, например, где каждый из них представляет из себя структурку с инжекцией методов сервисного слоя, а вариантов возможных ошибок овер много. Но нужна ли тут настолько глубокая тема, чтобы фактически вынести обработку ошибки на другой слой?) Если ваш http сервер на все эндпоинты возвращает клиенту кастомную ошибку, а не только статус - по логике это должно быть оформлено в виде отдельного метода (приватного), условно какой-нибудь func (h *Handler) handleError(w, r, err). Хоть через defer в теле HandleFunc вызывать можно)) А так круто, но сама идея поменять дефолтную сигнатуру эндпоинта для достижения обработки сервисных ошибок, как мне кажется - оверкилл)
Имеет право на жизнь, но с оговорками)
Архитектурный паттерн для централизованной обработки ошибок в хендлерах на Go