Где лучше обрабатывать ошибки 404 Not Found в API: в сервисе или в контроллере
Что проверяют
Разделяете ли вы бизнес-логику и транспорт. Интервьюер хочет услышать, что сервис не должен знать про HTTP, но и контроллер не должен дублировать проверки. Следом часто спрашивают, как тестировать такой сервис и что он вернёт, если его вызовет Celery-задача.
Как отвечать
Рабочая схема разделяет обнаружение ошибки и её перевод в HTTP:
- сервис обнаруживает: если объекта нет, он бросает доменное исключение, например
OrderNotFound(order_id). НикакихHTTPExceptionи статус-кодов: тот же сервис вызывают из фоновой задачи, CLI или gRPC, где 404 ничего не значит; - репозиторий обычно возвращает
None, а решение, ошибка это или норма, принимает сервис. Дляget_orderотсутствие это ошибка, дляfind_by_emailпри регистрации ожидаемый ответ; - HTTP-слой переводит: в FastAPI это один обработчик
@app.exception_handler(OrderNotFound), который отдаёт 404 со стабильным телом ошибки. В DRF то же делает свойEXCEPTION_HANDLERв настройках, во Flask@app.errorhandler; - контроллер остаётся тонким: вызывает сервис и возвращает результат, без
try/exceptв каждом эндпоинте.
В Django-вьюхе get_object_or_404 уместен: вьюха и есть HTTP-слой. Плохо, когда он переезжает в сервис.
Отдельно скажите про семантику. Пустой список по фильтру это 200 с [], а не 404. Если ресурс существует, но принадлежит другому пользователю, часто отдают 404 вместо 403, чтобы не раскрывать сам факт его существования.
Где спотыкаются
Бросают HTTPException прямо из сервиса. Код работает, но сервис намертво привязан к FastAPI, а в тестах вместо проверки бизнес-правила приходится разбирать HTTP-ответ.
Уходят в другую крайность: сервис возвращает None, а каждый контроллер сам проверяет результат и формирует 404. Через десяток эндпоинтов тексты ошибок и форматы расходятся.
Отвечают «зависит от проекта» и на этом останавливаются. Интервьюер ждёт конкретной схемы и её обоснования, а оговорки лучше приберечь для вопроса о границах подхода.